跳到主要内容

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 Cryptography 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 Cryptography API 实现几周前才完成,但等待是完全值得的。 Deno 通过的 web platform tests 比 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);

fetchRequestResponseHeaders

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 platform tests 测试套件进行测试。 这确保了 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 实现了 URLURLSearchParams API,正如 WHATWG 所指定的。 这意味着我们的 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/[email protected]?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/[email protected]?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 也将此相同的 API 用于本机 HTTP 服务器上的服务器端(传入)WebSocket 连接。

const ws = new WebSocket("ws://127.0.0.1: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,用于根据 path-to-regexp 风格的模式匹配 URL。它对于为 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 不同,它是 1 对多通道,而不是 1 对 1 通道。

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

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