本页内容
Web 平台 API
Deno 简化 Web 和云开发的一种方式是使用标准的 Web 平台 API(例如 fetch
、WebSockets 等)而非专有 API。这意味着,如果您曾为浏览器开发过,您可能已经熟悉 Deno;如果您正在学习 Deno,您同时也在提升您对 Web 知识的掌握。
下面我们将重点介绍 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 支持获取本地文件。详见下一节。 request
和response
头保护已实现,但与浏览器不同的是,它们对允许的头名称没有任何限制。referrer
、referrerPolicy
、mode
、credentials
、cache
、integrity
、keepalive
和window
属性及其在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.ts
和 lib.deno.window.d.ts
文件中找到。
worker 特定的定义可以在 lib.deno.worker.d.ts
文件中找到。
Location 跳转到标题
Deno 支持 Web 中的 location
全局对象。
Location 标志 跳转到标题
在 Deno 进程中没有可用于作为 location 的“网页”URL。我们允许用户通过在 CLI 上使用 --location
标志指定一个 URL 来模拟文档 location。它可以是 http
或 https
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.ts
、http://example.com/b.ts
和http://example.com:80/
等 location 都将共享相同的存储,但https://example.com/
将不同。 - 如果没有 location 指定符,但指定了
--config
配置文件,则使用该配置文件的绝对路径。这意味着deno run --config deno.jsonc a.ts
和deno 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
权限
new Worker(import.meta.resolve("./worker.ts"), { type: "module" });
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
权限
new Worker("https://example.com/worker.ts", { type: "module" });
// 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 跳转到标题
const worker = new Worker(import.meta.resolve("./worker.js"), {
type: "module",
});
worker.postMessage({ filename: "./log.txt" });
self.onmessage = async (e) => {
const { filename } = e.data;
const text = await Deno.readTextFile(filename);
console.log(text);
self.close();
};
hello world
$ deno run --allow-read main.js
hello world
指定 worker 权限 跳转到标题
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:
- CacheStorage::open()
- CacheStorage::has()
- CacheStorage::delete()
- Cache::match()
- Cache::put()
- Cache::delete()
与浏览器相比的一些不同之处:
- 您不能将相对路径传递给这些 API。请求可以是 Request 或 URL 的实例,也可以是 URL 字符串。
match()
和delete()
尚不支持查询选项。