跳到主要内容
Deno 2.4 已发布,带来 deno bundle、字节/文本导入、OTel 稳定版等新功能
了解更多

Deno 中的所有 Web API 列表

你是否曾好奇 Deno 的 Web 兼容性如何?也许没有,但我今天想到了。为了回答这个问题,我正在撰写这篇博客文章:我将列出并解释 Deno 中实现的每一个 Web API。给自己倒杯水吧,因为这篇文章内容很多,需要一些时间阅读。

不过,在深入探讨之前,我想先设定一些基本规则

  1. 我不包括任何 JS 语言特性。只包括 Web 平台特性。
  2. 我将包括少数仍标记为 --unstable 的特性,但会提及它们尚未稳定。
  3. 我不包括任何不是具体 API 的特性。例如,JSON 模块在 Deno 中有实现,但它们更像一个抽象概念而非具体 API,因此不在此列表中。

目录

atobbtoa

这两个函数用于编码和解码 base64 字符串。它们很古老了——2004 年 11 月 9 日发布的 Firefox 1 就已经支持了。

atob("SGVsbG8gV29ybGQ="); // "Hello World"
btoa("Hello World"); // "SGVsbG8gV29ybGQ="

setTimeoutclearTimeout

setTimeout 是另一个非常古老的 Web 特性。它用于安排一个函数在经过一定时间后被调用。它返回一个数字 ID,可以使用 clearTimeout 来取消该超时。

请注意,Deno 像浏览器一样实现了这个 API:返回一个数字。Node.js 也实现了这个 API,但其行为非标准,返回的是一个对象而不是数字。

setTimeout(() => {
  console.log("This prints after 1 second.");
}, 1000);

const timerId = setTimeout(() => {
  console.log("This doesn't print at all.");
}, 500);
clearTimeout(timerId);

console.log("This prints immediately.");

setIntervalclearInterval

setIntervalclearIntervalsetTimeoutclearTimeout 非常相似。区别在于 setInterval每隔 X 毫秒调用一次回调函数,而不是在 X 毫秒后只调用一次。

这里也适用与 setTimeout 相同的免责声明:Deno 像浏览器一样实现了这个 API,而 Node 的返回值是一个非标准对象。

crypto

Deno 完全实现了 Web 加密 API。这个 API 可用于执行各种低级和高级加密操作。例如,你可以:

  • 使用 crypto.randomUUID() 生成一个随机 UUID
  • 使用 crypto.getRandomValues() 生成一个填充了随机字节的 Uint8Array
  • 使用 crypto.subtle.sign()crypto.subtle.verify() 以及 RSA、ECDSA 和许多其他算法来签名和验证数据。
  • 使用 crypto.subtle.digest() 生成哈希。
  • 使用 crypto.subtle.encrypt()crypto.subtle.decrypt() 加密和解密数据。
  • 使用 crypto.subtle.generateKey() 生成 RSA、ECDSA、HMAC 或 AES 密钥

Deno 的 Web 加密 API 实现已于几周前完成,但等待是完全值得的。Deno 通过的 Web 平台测试比 Chrome 和 Firefox 都多。只有 Safari 略胜一筹。

引擎 通过测试的百分比
Safari 99.8%
Deno 98.1%
Chrome/Edge 94.5%
Firefox 93.4%

您可以在 wpt.fyi 上查看当前数据。

const uuid = crypto.randomUUID();
const bytes = await crypto.getRandomValues(new Uint8Array(16));
const digest = await crypto.subtle.digest("SHA-256", bytes);

fetch, Request, Response, 和 Headers

Deno 实现了可能是最流行的现代 Web API:fetch。它用于发起 HTTP 请求。用法非常简单:第一个参数是你想要请求的 URL,第二个参数是一个可选对象,包含 methodheadersbody 等选项。

Deno 中的 fetch API 是在运行时中原生实现的,由 Rust 中极速的 hyper HTTP 实现提供支持。它支持 HTTP/1.1 和 HTTP/2 服务器、全双工流传输、原生 gzip/deflate/brotli 解码,并且与 Web 平台高度兼容。

与我们所有其他 Web API 一样,我们的 fetch 实现使用与所有浏览器相同的 Web 平台测试套件进行测试。这确保了 Deno 的实现与 Web 平台兼容。据我们所知,目前我们是唯一一个针对规范 Web 平台测试来测试 fetch 的服务器端运行时。

Deno 还实现了 fetch 周围的所有对象:RequestResponseHeaders。这些对象分别代表一个 HTTP 请求、一个 HTTP 响应和一组 HTTP 头。由于 Deno 是一个 Web 优先的运行时,我们的 HTTP 服务器使用这些相同的对象来表示请求和响应。这使得代理请求变得非常容易。

const resp = await fetch("https://whats-my-ip.deno.dev/");
const text = await resp.text();
console.log(text);

BlobFile

BlobFile 都表示二进制数据。它们与 Uint8Array 的主要区别在于,它们可以将数据存储在内存或磁盘上,因此非常适合处理大型二进制 blob。由于数据可以存储在磁盘上,BlobFile 支持的数据只能异步获取。

可以使用 .arrayBuffer().text().stream() 方法或 FileReader API 来检索数据。

const blob = new Blob(["Hello World"]);
const text = await blob.text();
console.log(text);

const file = new File(["Hello World"], "hello.txt");
console.log(file.name);
console.log(file.size);
const bytes = await file.arrayBuffer();
console.log(new Uint8Array(bytes));

TextEncoderTextDecoder

有时你需要将字符串编码或解码为二进制表示形式。Deno 为此提供了 TextEncoderTextDecoder API。它们可用于将字符串编码为 Uint8Array,并将 Uint8Array 解码为字符串。浏览器中所有可用的文本编码在 Deno 中也可用。

const encoder = new TextEncoder();
const bytes = encoder.encode("Hello World");
console.log(bytes);

const decoder = new TextDecoder();
const text = decoder.decode(bytes);
console.log(text);

FormData

与 HTTP API 交互时,有时需要以 multipart/form-data 形式发送数据。这可以使用 FormData API 来完成。它是一个 JavaScript 结构,允许你轻松地将键值对或文件附加到表单中,以多部分数据形式发送。

由于 Deno 也为原生 HTTP 服务器使用与 fetch 相同的 RequestResponse 对象,因此可以通过调用 await request.formData() 轻松地从传入请求中解码多部分表单数据。很酷吧?

const formData = new FormData();
formData.append("name", "Deno");
formData.append("age", "3");
formData.append("file", new File(["Hello World"], "hello.txt"));

const resp = await fetch("https://httpbin.org/post", {
  method: "POST",
  body: formData,
});

performance

performance 允许精确的时间测量,例如使用 performance.now()。它还可以用于使用 performance.mark()performance.measure() 直接测量某个操作所花费的时间。

const start = performance.now();
await fetch("https://httpbin.org/delay/1");
const end = performance.now();
console.log("Took", end - start, "milliseconds");

structuredClone

structuredClone API 用于深度克隆对象。它是一个非常新的 API,但已在所有主流浏览器中发布。

与浅克隆相比,深克隆不仅复制最外层对象的结构,还复制所有嵌套对象和数组的结构。它还可以克隆带有循环引用的对象。

const obj = {
  foo: "bar",
  baz: {
    qux: "quux",
  },
};
const clone = structuredClone(obj);
console.log(obj === clone); // false
console.log(obj.baz === clone.baz); // false

obj.baz.qux = "quuz";
console.log(obj.baz.qux); // quuz
console.log(clone.baz.qux); // quux

URLURLSearchParams

URL 是任何与 Web 相关事物不可或缺的一部分。Deno 按照 WHATWG 的规范实现了 URLURLSearchParams API。这意味着我们的 URL 和查询字符串解析与浏览器完全相同。这可以防止一个系统与另一个系统解析 URL 略有不同而引发的安全问题。

const url = new URL("https://example.com/foo");
console.log(url.href); // https://example.com/foo
console.log(url.hostname); // example.com
console.log(url.searchParams.get("name")); // undefined
url.searchParams.append("name", "bar");
console.log(url.href); // https://example.com/foo?name=bar

console

console 可能是最有用的 Web API。有人用 Printf 调试吗?你可能已经知道 console.log() 了,所以这里有一些 Deno 实现的 console 的其他很酷的特性,你可能还不知道:

%c 标记可用于使用 CSS 为日志输出添加颜色。Deno 原生支持这一点——不再需要记住 ANSI 转义码来实现彩色日志记录了。

console.log("%cHello, world!", "color: red");
console.log("%cHello, %cworld!", "font-weight: bold", "font-style: italic");

我们实现的另一个很酷的 console 特性是 console.time API。它使得计算某个操作花费的时间变得非常简单。

console.time("foo");
await new Promise((resolve) => setTimeout(resolve, 1000));
console.timeEnd("foo");

Worker

JavaScript 执行始终是单线程的——该语言没有内置的并发原语。为了支持需要多线程的工作负载,Web 引入了 Worker 的概念。Worker 是在独立线程上运行的额外 JavaScript 执行上下文,与主线程完全隔离。

Deno 原生实现了 Worker,就像浏览器中一样。由于 API 是相同的,你可以在 Deno 中使用为浏览器编写的库,而无需任何更改。例如,comlink 就是这样一个库——它通过抽象掉跨 Worker 通信的细节,使得使用 Worker 变得非常简单。

// main.js
import * as Comlink from "https://cdn.skypack.dev/comlink@4.3.1?dts";

const url = new URL("./worker.js", import.meta.url);
const worker = new Worker(url, { type: "module" });

const obj = Comlink.wrap(worker);

console.log(`Counter: ${await obj.counter}`);
await obj.inc();
console.log(`Counter: ${await obj.counter}`);

worker.terminate();
// worker.js
import * as Comlink from "https://cdn.skypack.dev/comlink@4.3.1?dts";

const obj = {
  counter: 0,
  inc() {
    this.counter++;
  },
};

Comlink.expose(obj);

EventEventTarget

在浏览器中,事件和事件目标是所有交互体验的基础。它们允许你注册一个回调函数,以便在某个事件发生时被调用。例如,FileReader 就是一个 EventTarget:它在读取文件块时、文件完全读取时或发生错误时发出事件。

现代 API 通常不再使用事件和回调,而是使用 Promise。由于 async/await,它们具有更好的可用性和可读性。尽管如此,许多 API 仍在使用 EventEventTarget,因此 Deno 像浏览器一样实现了它们。

const target = new EventTarget();
target.addEventListener("foo", (event) => {
  console.log(event);
});
target.dispatchEvent(new Event("foo"));

WebSocket

就像 Deno 像浏览器一样实现 fetch 以进行 HTTP 请求一样,Deno 也实现了 WebSocket API。WebSocket 是实现客户端和服务器之间双向通信的绝佳方式。

由于 WebSocket 协议在连接的两端(客户端和服务器)工作方式相同,Deno 也为原生 HTTP 服务器上的服务器端(传入)WebSocket 连接使用相同的 API。

const ws = new WebSocket("ws://:4500");
ws.onopen = () => {
  ws.send("Hello, world!");
};
ws.onmessage = (event) => {
  console.log(event.data);
  ws.close();
};

ReadableStreamWritableStream

流式传输是现代 Web 应用程序的关键部分。当处理大于一次可用内存的数据时,它是必需的。它也是处理大文件时减少 TTFB 的好方法。

Deno 支持与浏览器相同的流 API。这非常强大,因为它允许 Deno 用户利用为浏览器编写的流转换器。它还允许 Web 开发人员在其网站上使用为 Deno 编写的流转换。例如,Deno 标准库中的 std/streams 模块可以在 Deno 和浏览器中使用,以实现诸如在换行符处分割文本流等功能。

可读流是最常见的流类型,用于从源读取数据。例如,你可能想要流式传输 HTTP 请求的响应体,或磁盘上文件的内容。

可写流则相反——它们用于当你想要分块将数据写入目标时。一个这样的例子是 WebSocketStream 的发送端(下文会详细介绍)。

const body = new ReadableStream({
  start(controller) {
    controller.enqueue(new Uint8Array([1, 2, 3]));
    controller.enqueue(new Uint8Array([4, 5, 6]));
    controller.close();
  },
});

const resp = await fetch("https://httpbin.org/anything", { body });
for await (const chunk of resp.body) {
  console.log(chunk);
}

TransformStream

除了低级流原语 ReadableStreamWritableStream 之外,Web 还有一个用于转换流式数据的通用 API,称为 TransformStream。它们有一个可读端和一个可写端,以及一个“转换器”函数。每当有数据块写入可写端时,都会调用转换器函数。然后,它可以将转换后的数据块排队到可读端,供消费者读取。

const input = new ReadableStream({
  start(controller) {
    controller.enqueue("Hello, ");
    controller.enqueue("world!");
    controller.close();
  },
});

const transformer = new TransformStream({
  transform(chunk, controller) {
    controller.enqueue(chunk.toUpperCase());
  },
});

const output = input.pipeThrough(transformer);

for await (const chunk of output) {
  console.log(chunk);
}

WebSocketStream

如前所述,Deno 实现了基于 EventTarget 构建的 WebSocket API。这有时使用起来相当痛苦。作为替代方案,你可以使用基于流和 Promise 的 WebSocketStream API。当你使用 async/await 时,它会更容易使用。

const wss = new WebSocketStream("wss://example.com");
const { writable, readable } = await wss.connection;

const writer = writable.getWriter();
await writer.write("Hello server!");

for await (const message of readable) {
  console.log("new message:", message);
}

注意:此 API 仍处于实验阶段,未来可能会更改。你需要使用 --unstable 标志才能使用它。

TextEncoderStreamTextDecoderStream

TextEncoderStream 和 TextDecoderStream 是转换流,可以将字符串分块编码为字节或从字节解码为字符串。与 TextEncoder 和 TextDecoder 通常用于完全同步操作不同,TextEncoderStream 和 TextDecoderStream 是完全流式的。

此 API 使得将 `fetch` 调用的响应流转换为字符串块变得非常容易。

const response = await fetch("https://example.com");
const body = response.body.pipeThrough(new TextDecoderStream());
for await (const chunk of body) {
  console.log(chunk);
}

CompressionStreamDecompressionStream

对流式数据进行处理的另一个常见操作是压缩和解压缩。例如,你可以使用 CompressionStream 在上传到存储桶之前对数据进行 gzip 压缩。再次下载该数据时,可以使用 DecompressionStream 对其进行解压缩。

与 Chrome 一样,Deno 支持 gzipdeflate 压缩。对 brotli 压缩的支持即将到来。

const response = await fetch("https://example.com");
const body = response.body.pipeThrough(new CompressionStream("gzip"));
const file = await Deno.create("./file.gz");
for await (const chunk of body) {
  await file.write(chunk);
}

CompressionStream 在最新的 Deno Canary 版本中可用,但尚未在 1.18 稳定版本中提供。它将首次在 Deno 1.19 稳定版本中提供。

URLPattern

URLPattern 是一个新的 Web API,用于将 URL 与 path-to-regexp 风格的模式进行匹配。例如,它对于为 HTTP 服务器创建路由系统非常有用。此 API 在 Chrome 和 Deno 中可用,Firefox 对实现它感兴趣。

const pattern = new URLPattern({ pathname: "/hello/:name" });
const match = pattern.exec("https://example.com/hello/Deno");
console.log(match.pathname.groups);

alert, confirmprompt

CLI 应用程序通常需要与用户进行一定程度的交互。Deno 为此目的实现了 Web 平台中简单的对话框 API alertconfirmprompt。你可以用消息 alert 用户并等待确认,要求用户 confirm 是/否问题,或者 prompt 用户提供字符串响应。

let name;
do {
  name = prompt("What is your name?");
} while (!confirm(`Is your name ${name}?`));
alert(`Hello, ${name}!`);

localStoragesessionStorage

在编写 CLI 应用程序时,跨运行持久化少量状态(例如 API 访问令牌)通常也很有用。这可以通过 localStorage API 轻松完成。它是每个 Deno 应用程序的持久性键值存储。

const count = parseInt(localStorage.getItem("count") || "0");
console.log(`You have started the application ${count} times previously.`);
localStorage.setItem("count", count + 1);

window.navigator 对象包含有关当前系统的一些有用信息,例如可用 CPU 核/线程的数量。navigator.hardwareConcurrency 包含逻辑 CPU 核的数量。

console.log("This system has", navigator.hardwareConcurrency, "CPU cores.");

WebGPU

之前的所有 API 都与 CPU 或某些 I/O 原语相关。Deno 还支持通过 WebGPU API 访问 GPU。这个低级 API 可以被认为是“Web 上的 Vulkan”。它允许高效地低级访问 GPU 以执行渲染和计算任务。

Deno 的 WebGPU 实现基于 Firefox 使用的相同 Rust 库:wgpu

const adapter = await navigator.gpu.requestAdapter();
console.log("GPU adapter:", adapter.name);

// this blog post is already long enough, I won't go into
// low level GPU programming here :-)

注意:此 API 仍处于实验阶段,未来可能会更改。你需要使用 --unstable 标志才能使用它。

MessageChannel

一个鲜为人知的 Web API 是 MessageChannel。它是一对流,可用于在两个 Worker 之间通信。每个通道都有两个“端口”,每个端口都可以保留在当前 Worker 中或传输到另一个 Worker。

这允许 Worker 之间建立非常复杂的通信通道,而无需单个集中式 Worker 充当“消息代理”。

const channel = new MessageChannel();
const { port1, port2 } = channel;
port1.onmessage = (event) => {
  console.log(event.data);
  port1.close();
};
port2.postMessage("Hello, world!");
port2.close();

BroadcastChannel

BroadcastChannel 与 MessageChannel 类似,它也是一个用于 Worker 之间通信的通道。然而,与 MessageChannel 不同的是,它是一个一对多的通道,而不是一对一的通道。

const channel = new BroadcastChannel("my-channel");
channel.onmessage = (event) => {
  console.log(event.data);
};
channel.postMessage("Hello, world!");

注意:此 API 仍处于实验阶段,未来可能会更改。你需要使用 --unstable 标志才能使用它。