deno.com
本页内容

Web 平台 API

Deno 简化 Web 和云开发的一种方式是使用标准的 Web 平台 API(例如 fetch、WebSockets 等)而非专有 API。这意味着,如果您曾为浏览器开发过,您可能已经熟悉 Deno;如果您正在学习 Deno,您同时也在提升您对 Web 知识的掌握。

探索支持的 Web API

下面我们将重点介绍 Deno 支持的一些标准 Web API。

要检查某个 Web 平台 API 在 Deno 中是否可用,您可以点击 MDN 上的接口 并参考 其浏览器兼容性表

fetch 跳转到标题

fetch API 可用于发起 HTTP 请求。它按照 WHATWG fetch 规范 中的规定实现。

规范偏差 跳转到标题

  • Deno 用户代理不包含 cookie jar。因此,响应上的 set-cookie 头不会被处理,也不会从可见的响应头中被过滤。
  • Deno 不遵循同源策略,因为 Deno 用户代理目前没有源(origin)的概念,并且它也不包含 cookie jar。这意味着 Deno 不需要防范跨源泄露已认证数据。因此,Deno 不实现 WHATWG fetch 规范的以下部分:
    • 3.1.'Origin' 头
    • 3.2.CORS 协议
    • 3.5.CORB
    • 3.6.'Cross-Origin-Resource-Policy' 头
    • 原子性 HTTP 重定向处理.
    • opaqueredirect 响应类型。
  • 使用 manual 重定向模式的 fetch 将返回 basic 响应,而非 opaqueredirect 响应。
  • 该规范对 file: URL 的处理方式 描述模糊。Firefox 是唯一实现 file: URL 获取的主流浏览器,即便如此,它默认也不起作用。自 Deno 1.16 起,Deno 支持获取本地文件。详见下一节。
  • requestresponse 头保护已实现,但与浏览器不同的是,它们对允许的头名称没有任何限制。
  • referrerreferrerPolicymodecredentialscacheintegritykeepalivewindow 属性及其在 RequestInit 中的相关行为均未实现。相关的字段不存在于 Request 对象上。
  • 请求体上传流式传输受支持(在 HTTP/1.1 和 HTTP/2 上)。与当前的 fetch 提案不同,此实现支持双工流式传输。
  • headers 迭代器中迭代时,set-cookie 头不会被拼接。这种行为正在 规范化过程中

获取本地文件 跳转到标题

Deno 支持获取 file: URL。这使得编写在服务器和本地使用相同代码路径的代码变得更容易,同时也更容易编写与 Deno CLI 和 Deno Deploy 均兼容的代码。

Deno 仅支持绝对文件 URL,这意味着 fetch("./some.json") 将无法工作。但需要注意的是,如果指定了 --location,相对 URL 将使用 --location 作为基准,但 file: URL 不能作为 --location 传递。

为了能够获取相对于当前模块的资源(无论模块是本地的还是远程的都能工作),您应该使用 import.meta.url 作为基准。例如:

const response = await fetch(new URL("./config.json", import.meta.url));
const config = await response.json();

获取本地文件的注意事项

  • 权限适用于读取资源,因此需要适当的 --allow-read 权限才能读取本地文件。
  • 本地获取仅支持 GET 方法,使用任何其他方法都将拒绝 promise。
  • 不存在的文件会简单地以一个模糊的 TypeError 拒绝 promise。这是为了避免潜在的指纹识别攻击。
  • 响应上未设置任何头。因此,内容类型或内容长度等事项由消费者自行确定。
  • 响应体从 Rust 端流式传输,因此大文件可以分块提供,并且可以取消。

CustomEvent 和 EventTarget 跳转到标题

DOM Event API 可用于分派和监听应用程序中发生的事件。它按照 WHATWG DOM 规范 中的规定实现。

规范偏差 跳转到标题

  • 事件不会冒泡,因为 Deno 没有 DOM 层次结构,所以没有事件可以冒泡/捕获的树。
  • timeStamp 属性始终设置为 0

类型定义 跳转到标题

已实现的 Web API 的 TypeScript 定义可以在 lib.deno.shared_globals.d.tslib.deno.window.d.ts 文件中找到。

worker 特定的定义可以在 lib.deno.worker.d.ts 文件中找到。

Location 跳转到标题

Deno 支持 Web 中的 location 全局对象。

Location 标志 跳转到标题

在 Deno 进程中没有可用于作为 location 的“网页”URL。我们允许用户通过在 CLI 上使用 --location 标志指定一个 URL 来模拟文档 location。它可以是 httphttps URL。

// deno run --location https://example.com/path main.ts

console.log(location.href);
// "https://example.com/path"

您必须传递 --location <href> 才能使其工作。如果不这样做,任何对 location 全局对象的访问都将抛出错误。

// deno run main.ts

console.log(location.href);
// error: Uncaught ReferenceError: Access to "location", run again with --location <href>.

在浏览器中,设置 location 或其任何字段通常会导致导航。这在 Deno 中不适用,因此在这种情况下会抛出错误。

// deno run --location https://example.com/path main.ts

location.pathname = "./foo";
// error: Uncaught NotSupportedError: Cannot set "location.pathname".

扩展用法 跳转到标题

在 Web 上,资源解析(不包括模块)通常使用 location.href 的值作为解析任何相对 URL 的根。这会影响 Deno 采用的一些 Web API。

Fetch API 跳转到标题

// deno run --location https://api.github.com/ --allow-net main.ts

const response = await fetch("./orgs/denoland");
// Fetches "https://api.github.com/orgs/denoland".

如果未传递 --location 标志,上述 fetch() 调用将抛出错误,因为没有与 Web 类似的 location 可以作为其基准。

Worker 模块 跳转到标题

// deno run --location https://example.com/index.html --allow-net main.ts

const worker = new Worker("./workers/hello.ts", { type: "module" });
// Fetches worker module at "https://example.com/workers/hello.ts".

注意

对于上述用例,最好传递完整的 URL 而不是依赖 --location。如果需要,您可以使用 URL 构造函数手动构建相对 URL。

--location 标志旨在供那些有特定目的需要模拟文档 location 并了解这仅在应用程序级别起作用的用户使用。但是,您也可以使用它来抑制某个轻率访问 location 全局对象的依赖项所产生的错误。

Web Storage 跳转到标题

Web Storage API 提供了一个用于存储字符串键值对的 API。数据持久化与浏览器类似,并有 10MB 的存储限制。全局 sessionStorage 对象仅在当前执行上下文中持久化数据,而 localStorage 则在不同执行之间持久化数据。

在浏览器中,localStorage 按源(有效地是协议加上主机名加上端口)唯一地持久化数据。自 Deno 1.16 起,Deno 有一套规则来确定什么是唯一的存储位置:

  • 当使用 --location 标志时,location 的源用于唯一地存储数据。这意味着 http://example.com/a.tshttp://example.com/b.tshttp://example.com:80/ 等 location 都将共享相同的存储,但 https://example.com/ 将不同。
  • 如果没有 location 指定符,但指定了 --config 配置文件,则使用该配置文件的绝对路径。这意味着 deno run --config deno.jsonc a.tsdeno run --config deno.jsonc b.ts 将共享相同的存储,但 deno run --config tsconfig.json a.ts 将不同。
  • 如果没有配置或 location 指定符,Deno 使用主模块的绝对路径来确定共享存储。Deno REPL 会生成一个基于当前工作目录(即 deno 启动位置)的“合成”主模块。这意味着从同一路径多次调用 REPL 将共享持久化的 localStorage 数据。

要从 localStorage 中设置、获取和删除项目,您可以使用以下方式:

// Set an item in localStorage
localStorage.setItem("myDemo", "Deno App");

// Read an item from localStorage
const cat = localStorage.getItem("myDemo");

// Remove an item from localStorage
localStorage.removeItem("myDemo");

// Remove all items from localStorage
localStorage.clear();

Web Workers 跳转到标题

Deno 支持 Web Worker API

Worker 可用于在多个线程上运行代码。每个 Worker 实例都在一个独立的线程上运行,该线程仅专用于该 worker。

目前 Deno 仅支持 module 类型的 worker;因此,在创建新 worker 时,必须传递 type: "module" 选项。

主 worker 中相对模块说明符的使用仅在 CLI 上传递 --location <href> 时受支持。为了可移植性,不建议这样做。您可以改用 URL 构造函数和 import.meta.url 轻松为附近的某个脚本创建说明符。然而,专用 worker 默认具有 location 和此功能。

// Good
new Worker(import.meta.resolve("./worker.js"), { type: "module" });

// Bad
new Worker(import.meta.resolve("./worker.js"));
new Worker(import.meta.resolve("./worker.js"), { type: "classic" });
new Worker("./worker.js", { type: "module" });

与常规模块一样,您可以在 worker 模块中使用顶级 await。但是,您应该小心,务必在第一个 await 之前注册消息处理程序,否则消息可能会丢失。这不是 Deno 中的 bug,它只是功能之间不幸的交互,并且在所有支持模块 worker 的浏览器中也会发生。

import { delay } from "jsr:@std/async@1/delay";

// First await: waits for a second, then continues running the module.
await delay(1000);

// The message handler is only set after that 1s delay, so some of the messages
// that reached the worker during that second might have been fired when no
// handler was registered.
self.onmessage = (evt) => {
  console.log(evt.data);
};

实例化权限 跳转到标题

创建新的 Worker 实例类似于动态导入;因此 Deno 需要此操作的相应权限。

对于使用本地模块的 worker;需要 --allow-read 权限

main.ts
new Worker(import.meta.resolve("./worker.ts"), { type: "module" });
worker.ts
console.log("hello world");
self.close();
$ deno run main.ts
error: Uncaught PermissionDenied: read access to "./worker.ts", run again with the --allow-read flag

$ deno run --allow-read main.ts
hello world

对于使用远程模块的 worker;需要 --allow-net 权限

main.ts
new Worker("https://example.com/worker.ts", { type: "module" });
worker.ts
// This file is hosted at https://example.com/worker.ts
console.log("hello world");
self.close();
$ deno run main.ts
error: Uncaught PermissionDenied: net access to "https://example.com/worker.ts", run again with the --allow-net flag

$ deno run --allow-net main.ts
hello world

在 worker 中使用 Deno 跳转到标题

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

worker.postMessage({ filename: "./log.txt" });
worker.js
self.onmessage = async (e) => {
  const { filename } = e.data;
  const text = await Deno.readTextFile(filename);
  console.log(text);
  self.close();
};
log.txt
hello world
$ deno run --allow-read main.js
hello world

指定 worker 权限 跳转到标题

注意

这是一个不稳定的 Deno 功能。了解更多关于 不稳定功能 的信息。

worker 可用的权限类似于 CLI 权限标志,这意味着在那里启用的每个权限都可以在 Worker API 层面禁用。您可以在此处找到每个权限选项的更详细描述。

默认情况下,worker 将继承创建它的线程的权限,但是为了允许用户限制此 worker 的访问权限,我们在 worker API 中提供了 deno.permissions 选项。

对于支持细粒度访问的权限,您可以传入 worker 将有权访问的所需资源列表;对于只有开/关选项的权限,您可以分别传入 true/false。

const worker = new Worker(import.meta.resolve("./worker.js"), {
  type: "module",
  deno: {
    permissions: {
      net: [
        "deno.land",
      ],
      read: [
        new URL("./file_1.txt", import.meta.url),
        new URL("./file_2.txt", import.meta.url),
      ],
      write: false,
    },
  },
});

细粒度访问权限接受绝对路径和相对路径作为参数,但请注意,相对路径将相对于实例化 worker 的文件解析,而不是 worker 文件当前所在的路径。

const worker = new Worker(
  new URL("./worker/worker.js", import.meta.url).href,
  {
    type: "module",
    deno: {
      permissions: {
        read: [
          "/home/user/Documents/deno/worker/file_1.txt",
          "./worker/file_2.txt",
        ],
      },
    },
  },
);

deno.permissions 及其子项都支持 "inherit" 选项,这意味着它将继承其父权限。

// This worker will inherit its parent permissions
const worker = new Worker(import.meta.resolve("./worker.js"), {
  type: "module",
  deno: {
    permissions: "inherit",
  },
});
// This worker will inherit only the net permissions of its parent
const worker = new Worker(import.meta.resolve("./worker.js"), {
  type: "module",
  deno: {
    permissions: {
      env: false,
      hrtime: false,
      net: "inherit",
      ffi: false,
      read: false,
      run: false,
      write: false,
    },
  },
});

不指定 deno.permissions 选项或其任何子项将导致 worker 默认继承权限。

// This worker will inherit its parent permissions
const worker = new Worker(import.meta.resolve("./worker.js"), {
  type: "module",
});
// This worker will inherit all the permissions of its parent BUT net
const worker = new Worker(import.meta.resolve("./worker.js"), {
  type: "module",
  deno: {
    permissions: {
      net: false,
    },
  },
});

通过将 "none" 传递给 deno.permissions 选项,您可以完全禁用 worker 的权限。

// This worker will not have any permissions enabled
const worker = new Worker(import.meta.resolve("./worker.js"), {
  type: "module",
  deno: {
    permissions: "none",
  },
});

其他 API 的规范偏差 跳转到标题

Cache API 跳转到标题

仅实现以下 API:

与浏览器相比的一些不同之处:

  1. 您不能将相对路径传递给这些 API。请求可以是 Request 或 URL 的实例,也可以是 URL 字符串。
  2. match()delete() 尚不支持查询选项。

您找到所需内容了吗?

隐私政策