deno.com
在本页中

Deno 命名空间 API

全局 `Deno` 命名空间包含非 Web 标准的 API,包括用于读取文件、打开 TCP 套接字、提供 HTTP 服务和执行子进程等的 API。

探索所有 Deno API

下面我们重点介绍一些最重要的 Deno API。

文件系统 跳转到标题

Deno 运行时内置了各种用于处理文件和目录的函数。你需要使用 --allow-read 和 --allow-write 权限才能访问文件系统。

请参考以下链接,查看如何使用文件系统函数的代码示例。

网络 跳转到标题

Deno 运行时内置了用于处理网络端口连接的内置函数

请参考以下链接,查看常用函数的代码示例。

子进程 跳转到标题

Deno 运行时内置了用于启动子进程的内置函数

请参考以下链接,查看如何创建子进程的代码示例。

错误 跳转到标题

Deno 运行时内置了20 个错误类,可以在多种情况下引发。

一些例子是

Deno.errors.NotFound;
Deno.errors.WriteZero;

它们可以像下面这样使用

try {
  const file = await Deno.open("./some/file.txt");
} catch (error) {
  if (error instanceof Deno.errors.NotFound) {
    console.error("the file was not found");
  } else {
    // otherwise re-throw
    throw error;
  }
}

HTTP 服务器 跳转到标题

Deno 有两个 HTTP 服务器 API

  • `Deno.serve`:原生,更高级别,支持 HTTP/1.1 和 HTTP2,这是在 Deno 中编写 HTTP 服务器的首选 API。
  • `Deno.serveHttp`:原生,低级别,支持 HTTP/1.1 和 HTTP2。

要在给定端口上启动 HTTP 服务器,请使用 `Deno.serve` 函数。此函数接受一个处理函数,该函数将为每个传入的请求调用,并且期望返回一个响应(或解析为响应的 Promise)。例如

Deno.serve((_req) => {
  return new Response("Hello, World!");
});

默认情况下,`Deno.serve` 将监听端口 `8000`,但这可以通过在选项包中传入端口号作为第一个或第二个参数来更改。

你可以阅读更多关于如何使用 HTTP 服务器 API 的信息

权限 跳转到标题

权限在运行 `deno` 命令时从 CLI 授予。用户代码通常会假定自己所需的一组权限,但是在执行期间,不能保证**授予**的权限集会与此对齐。

在某些情况下,确保容错程序需要一种在运行时与权限系统交互的方式。

权限描述符 跳转到标题

在 CLI 中,`/foo/bar` 的读取权限表示为 `--allow-read=/foo/bar`。在运行时 JS 中,它表示为以下内容

const desc = { name: "read", path: "/foo/bar" } as const;

其他例子

// Global write permission.
const desc1 = { name: "write" } as const;

// Write permission to `$PWD/foo/bar`.
const desc2 = { name: "write", path: "foo/bar" } as const;

// Global net permission.
const desc3 = { name: "net" } as const;

// Net permission to 127.0.0.1:8000.
const desc4 = { name: "net", host: "127.0.0.1:8000" } as const;

// High-resolution time permission.
const desc5 = { name: "hrtime" } as const;

有关更多详细信息,请参阅 API 参考中的 `PermissionDescriptor`。所有以下描述的 API 都存在同步 API 对等项 (例如 `Deno.permissions.querySync`)。

查询权限 跳转到标题

通过描述符检查权限是否已授予。

// deno run --allow-read=/foo main.ts

const desc1 = { name: "read", path: "/foo" } as const;
console.log(await Deno.permissions.query(desc1));
// PermissionStatus { state: "granted", partial: false }

const desc2 = { name: "read", path: "/foo/bar" } as const;
console.log(await Deno.permissions.query(desc2));
// PermissionStatus { state: "granted", partial: false }

const desc3 = { name: "read", path: "/bar" } as const;
console.log(await Deno.permissions.query(desc3));
// PermissionStatus { state: "prompt", partial: false }

如果使用 `--deny-read` 标志来限制某些文件路径,则结果将包含 `partial: true`,描述并非所有子路径都已授予权限

// deno run --allow-read=/foo --deny-read=/foo/bar main.ts

const desc1 = { name: "read", path: "/foo" } as const;
console.log(await Deno.permissions.query(desc1));
// PermissionStatus { state: "granted", partial: true }

const desc2 = { name: "read", path: "/foo/bar" } as const;
console.log(await Deno.permissions.query(desc2));
// PermissionStatus { state: "denied", partial: false }

const desc3 = { name: "read", path: "/bar" } as const;
console.log(await Deno.permissions.query(desc3));
// PermissionStatus { state: "prompt", partial: false }

权限状态 跳转到标题

权限状态可以是 "granted"、"prompt" 或 "denied"。从 CLI 授予的权限将查询到 `{ state: "granted" }`。默认情况下,未授予的权限将查询到 `{ state: "prompt" }`,而 `{ state: "denied" }` 保留给那些被明确拒绝的权限。这将在请求权限中出现。

权限强度 跳转到标题

查询权限中第二个查询结果背后的直观理解是,已授予 `/foo` 的读取权限,并且 `/foo/bar` 在 `/foo` 内,因此允许读取 `/foo/bar`。除非 CLI 授予的权限对于查询的权限是*部分的*(作为使用 `--deny-*` 标志的效果),否则这是成立的。

我们也可以说 `desc1` *强于* `desc2`。这意味着对于任何 CLI 授予的权限集

  1. 如果 `desc1` 查询到 `{ state: "granted", partial: false }`,那么 `desc2` 也必须如此。
  2. 如果 `desc2` 查询到 `{ state: "denied", partial: false }`,那么 `desc1` 也必须如此。

更多例子

const desc1 = { name: "write" } as const;
// is stronger than
const desc2 = { name: "write", path: "/foo" } as const;

const desc3 = { name: "net", host: "127.0.0.1" } as const;
// is stronger than
const desc4 = { name: "net", host: "127.0.0.1:8000" } as const;

请求权限 跳转到标题

通过 CLI 提示符向用户请求未授予的权限。

// deno run main.ts

const desc1 = { name: "read", path: "/foo" } as const;
const status1 = await Deno.permissions.request(desc1);
// ⚠️ Deno requests read access to "/foo". Grant? [y/n (y = yes allow, n = no deny)] y
console.log(status1);
// PermissionStatus { state: "granted", partial: false }

const desc2 = { name: "read", path: "/bar" } as const;
const status2 = await Deno.permissions.request(desc2);
// ⚠️ Deno requests read access to "/bar". Grant? [y/n (y = yes allow, n = no deny)] n
console.log(status2);
// PermissionStatus { state: "denied", partial: false }

如果当前权限状态为 "prompt",则用户的终端上将出现提示,询问他们是否要授予请求。对 `desc1` 的请求已授予,因此返回其新状态,并且执行将继续,就像在 CLI 上指定了 `--allow-read=/foo` 一样。对 `desc2` 的请求被拒绝,因此其权限状态从 "prompt" 降级为 "denied"。

如果当前权限状态已经是 "granted" 或 "denied",则请求的行为将类似于查询,并且仅返回当前状态。这样可以防止对已授予的权限和先前拒绝的请求都发出提示。

撤销权限 跳转到标题

将权限从 "granted" 降级为 "prompt"。

// deno run --allow-read=/foo main.ts

const desc = { name: "read", path: "/foo" } as const;
console.log(await Deno.permissions.revoke(desc));
// PermissionStatus { state: "prompt", partial: false }

当你尝试撤销一个权限时,该权限是 CLI 上授予的权限的*部分*,会发生什么情况?

// deno run --allow-read=/foo main.ts

const desc = { name: "read", path: "/foo/bar" } as const;
console.log(await Deno.permissions.revoke(desc));
// PermissionStatus { state: "prompt", partial: false }
const cliDesc = { name: "read", path: "/foo" } as const;
console.log(await Deno.permissions.revoke(cliDesc));
// PermissionStatus { state: "prompt", partial: false }

CLI 授予的权限(暗示撤销的权限)也被撤销。

为了理解这种行为,请想象一下 Deno 存储了一组内部的*显式授予的权限描述符*。在 CLI 上指定 `--allow-read=/foo,/bar` 会将此集合初始化为

[
  { name: "read", path: "/foo" },
  { name: "read", path: "/bar" },
];

授予对 `{ name: "write", path: "/foo" }` 的运行时请求会将集合更新为

[
  { name: "read", path: "/foo" },
  { name: "read", path: "/bar" },
  { name: "write", path: "/foo" },
];

Deno 的权限撤销算法通过从该集合中删除每个*强于*参数权限描述符的元素来工作。

Deno 不允许“碎片化”的权限状态,其中某些强权限被授予,但排除了它所暗示的弱权限。当你考虑更广泛的用例和 `"denied"` 状态时,这样的系统将被证明越来越复杂且不可预测。这是为了安全而对粒度进行权衡的计算结果。

import.meta 跳转到标题

Deno 支持 `import.meta` API 上的许多属性和方法。它可以用于获取有关模块的信息,例如模块的 URL。

import.meta.url 跳转到标题

返回当前模块的 URL。

main.ts
console.log(import.meta.url);
$ deno run main.ts
file:///dev/main.ts

$ deno run https:/example.com/main.ts
https://example.com/main.ts

import.meta.main 跳转到标题

返回当前模块是否是你程序的入口点。

main.ts
import "./other.ts";

console.log(`Is ${import.meta.url} the main module?`, import.meta.main);
other.ts
console.log(`Is ${import.meta.url} the main module?`, import.meta.main);
$ deno run main.ts
Is file:///dev/other.ts the main module? false
Is file:///dev/main.ts the main module? true

import.meta.filename 跳转到标题

此属性仅适用于本地模块(具有 `file:///...` 说明符的模块),对于远程模块返回 `undefined`。

返回当前模块的完全解析路径。该值包含特定于操作系统的路径分隔符。

main.ts
console.log(import.meta.filename);

在 Unix 上

$ deno run main.ts
/dev/main.ts

$ deno run https://example.com/main.ts
undefined

在 Windows 上

$ deno run main.ts
C:\dev\main.ts

$ deno run https://example.com/main.ts
undefined

import.meta.dirname 跳转到标题

此属性仅适用于本地模块(具有 `file:///...` 说明符的模块),对于远程模块返回 `undefined`。

返回当前模块所在目录的完全解析路径。该值包含特定于操作系统的路径分隔符。

main.ts
console.log(import.meta.dirname);

在 Unix 上

$ deno run main.ts
/dev/

$ deno run https://example.com/main.ts
undefined

在 Windows 上

$ deno run main.ts
C:\dev\

$ deno run https://example.com/main.ts
undefined

import.meta.resolve 跳转到标题

解析相对于当前模块的说明符。

const worker = new Worker(import.meta.resolve("./worker.ts"));

`import.meta.resolve` API 考虑了当前应用的导入映射,这使你能够解析“裸”说明符。

加载了这样的导入映射...

{
  "imports": {
    "fresh": "https://deno.land/x/fresh@1.0.1/dev.ts"
  }
}

...你现在可以解析

resolve.js
console.log(import.meta.resolve("fresh"));
$ deno run resolve.js
https://deno.land/x/fresh@1.0.1/dev.ts

FFI 跳转到标题

FFI(外部函数接口)API 允许用户使用 `Deno.dlopen` 调用以支持 C ABI 的原生语言(C/C++、Rust、Zig、V 等)编写的库。

这是一个示例,展示了如何从 Deno 调用 Rust 函数

// add.rs
#[no_mangle]
pub extern "C" fn add(a: isize, b: isize) -> isize {
    a + b
}

将其编译为 C 动态库(Linux 上的 `libadd.so`)

rustc --crate-type cdylib add.rs

在 C 中,你可以这样编写

// add.c
int add(int a, int b) {
  return a + b;
}

并编译它

// unix
cc -c -o add.o add.c
cc -shared -W -o libadd.so add.o
// Windows
cl /LD add.c /link /EXPORT:add

从 Deno 调用库

// ffi.ts

// Determine library extension based on
// your OS.
let libSuffix = "";
switch (Deno.build.os) {
  case "windows":
    libSuffix = "dll";
    break;
  case "darwin":
    libSuffix = "dylib";
    break;
  default:
    libSuffix = "so";
    break;
}

const libName = `./libadd.${libSuffix}`;
// Open library and define exported symbols
const dylib = Deno.dlopen(
  libName,
  {
    "add": { parameters: ["isize", "isize"], result: "isize" },
  } as const,
);

// Call the symbol `add`
const result = dylib.symbols.add(35, 34); // 69

console.log(`Result from external addition of 35 and 34: ${result}`);

使用 `--allow-ffi` 和 `--unstable` 标志运行

deno run --allow-ffi --unstable ffi.ts

非阻塞 FFI 跳转到标题

在许多用例中,用户可能希望在后台运行 CPU 密集型 FFI 函数,而不会阻塞主线程上的其他任务。

从 Deno 1.15 开始,可以在 `Deno.dlopen` 中将符号标记为 `nonblocking`。这些函数调用将在专用阻塞线程上运行,并将返回解析为所需 `result` 的 `Promise`。

使用 Deno 执行昂贵的 FFI 调用的示例

// sleep.c
#ifdef _WIN32
#include 
#else
#include 
#endif

int sleep(unsigned int ms) {
  #ifdef _WIN32
  Sleep(ms);
  #else
  struct timespec ts;
  ts.tv_sec = ms / 1000;
  ts.tv_nsec = (ms % 1000) * 1000000;
  nanosleep(&ts, NULL);
  #endif
}

从 Deno 调用它

// nonblocking_ffi.ts
const library = Deno.dlopen(
  "./sleep.so",
  {
    sleep: {
      parameters: ["usize"],
      result: "void",
      nonblocking: true,
    },
  } as const,
);

library.symbols.sleep(500).then(() => console.log("After"));
console.log("Before");

结果

$ deno run --allow-ffi --unstable unblocking_ffi.ts
Before
After

回调 跳转到标题

Deno FFI API 支持从 JavaScript 函数创建 C 回调,以便从动态库回调到 Deno。以下是如何创建和使用回调的示例

// callback_ffi.ts
const library = Deno.dlopen(
  "./callback.so",
  {
    set_status_callback: {
      parameters: ["function"],
      result: "void",
    },
    start_long_operation: {
      parameters: [],
      result: "void",
    },
    check_status: {
      parameters: [],
      result: "void",
    },
  } as const,
);

const callback = new Deno.UnsafeCallback(
  {
    parameters: ["u8"],
    result: "void",
  } as const,
  (success: number) => {},
);

// Pass the callback pointer to dynamic library
library.symbols.set_status_callback(callback.pointer);
// Start some long operation that does not block the thread
library.symbols.start_long_operation();

// Later, trigger the library to check if the operation is done.
// If it is, this call will trigger the callback.
library.symbols.check_status();

如果 `UnsafeCallback` 的回调函数抛出错误,则该错误将传播到触发回调调用的函数(在上面,这将是 `check_status()`),并且可以在那里捕获。如果返回值的回调抛出错误,则 Deno 将返回 0(指针的空指针)作为结果。

默认情况下,`UnsafeCallback` 不会被释放,因为它可能导致释放后使用错误。要正确处理 `UnsafeCallback`,必须调用其 `close()` 方法。

const callback = new Deno.UnsafeCallback(
  { parameters: [], result: "void" } as const,
  () => {},
);

// After callback is no longer needed
callback.close();
// It is no longer safe to pass the callback as a parameter.

原生库也可以设置中断处理程序,并让这些处理程序直接触发回调。但是,不建议这样做,并且可能会导致意外的副作用和未定义的行为。最好任何中断处理程序都只设置一个标志,该标志可以稍后轮询,类似于上面 `check_status()` 的使用方式。

支持的类型 跳转到标题

这是 Deno FFI API 当前支持的类型列表。

FFI 类型 Deno C Rust
i8 number `char` / `signed char` i8
u8 number unsigned char u8
i16 number short int i16
u16 number unsigned short int u16
i32 number `int` / `signed int` i32
u32 number unsigned int u32
i64 bigint long long int i64
u64 bigint unsigned long long int u64
usize bigint size_t usize
isize bigint size_t isize
f32 number float f32
f64 number double f64
void[1] undefined void ()
pointer {} | null void * *mut c_void
buffer[2] TypedArray | null uint8_t * *mut u8
function[3] {} | null void (*fun)() Option<extern "C" fn()>
{ struct: [...] }[4] TypedArray struct MyStruct MyStruct

从 Deno 1.25 开始,`pointer` 类型已拆分为 `pointer` 和 `buffer` 类型,以确保用户利用 Typed Arrays 的优化,并且从 Deno 1.31 开始,`pointer` 的 JavaScript 表示形式已变为不透明指针对象或空指针的 `null`。

  • [1] `void` 类型只能用作结果类型。
  • [2] `buffer` 类型接受 TypedArrays 作为参数,但当用作结果类型时,它总是返回指针对象或 `null`,就像 `pointer` 类型一样。
  • [3] `function` 类型作为参数和结果类型的工作方式与 `pointer` 类型完全相同。
  • [4] `struct` 类型用于按值(复制)传递和返回 C 结构体。`struct` 数组必须按顺序枚举结构体每个字段的类型。结构体会自动填充:可以通过使用适当数量的 `u8` 字段来避免填充,从而定义打包结构体。只有 TypedArrays 支持作为结构体,并且结构体始终作为 `Uint8Array` 返回。

deno_bindgen 跳转到标题

`deno_bindgen` 是官方工具,用于简化用 Rust 编写的 Deno FFI 库的胶水代码生成。

它类似于 Rust Wasm 生态系统中的 `wasm-bindgen`

这是一个示例,展示了它的用法

// mul.rs
use deno_bindgen::deno_bindgen;

#[deno_bindgen]
struct Input {
  a: i32,
  b: i32,
}

#[deno_bindgen]
fn mul(input: Input) -> i32 {
  input.a * input.b
}

运行 `deno_bindgen` 以生成绑定。你现在可以直接将它们导入到 Deno 中

// mul.ts
import { mul } from "./bindings/bindings.ts";
mul({ a: 10, b: 2 }); // 20

与 `deno_bindgen` 相关的任何问题都应报告在 https://github.com/denoland/deno_bindgen/issues

程序生命周期 跳转到标题

Deno 支持浏览器兼容的生命周期事件

  • `load`:当整个页面已加载时触发,包括所有依赖资源,例如样式表和图像。
  • `beforeunload`:当事件循环没有更多工作要做并且即将退出时触发。安排更多异步工作(如计时器或网络请求)将导致程序继续运行。
  • `unload`:当文档或子资源正在卸载时触发。
  • `unhandledrejection`:当没有拒绝处理程序的 Promise 被拒绝时触发,即没有 `.catch()` 处理程序或 `.then()` 的第二个参数的 Promise。
  • `rejectionhandled`:当 `.catch()` 处理程序添加到已拒绝的 Promise 时触发。仅当安装了 `unhandledrejection` 侦听器以防止事件传播(这将导致程序因错误而终止)时,才会触发此事件。

你可以使用这些事件在你的程序中提供设置和清理代码。

`load` 事件的侦听器可以是异步的,并且将被等待,此事件无法取消。`beforeunload` 的侦听器需要是同步的,并且可以取消以保持程序运行。`unload` 事件的侦听器需要是同步的,并且无法取消。

main.ts

main.ts
import "./imported.ts";

const handler = (e: Event): void => {
  console.log(`got ${e.type} event in event handler (main)`);
};

globalThis.addEventListener("load", handler);

globalThis.addEventListener("beforeunload", handler);

globalThis.addEventListener("unload", handler);

globalThis.onload = (e: Event): void => {
  console.log(`got ${e.type} event in onload function (main)`);
};

globalThis.onbeforeunload = (e: Event): void => {
  console.log(`got ${e.type} event in onbeforeunload function (main)`);
};

globalThis.onunload = (e: Event): void => {
  console.log(`got ${e.type} event in onunload function (main)`);
};

console.log("log from main script");
imported.ts
const handler = (e: Event): void => {
  console.log(`got ${e.type} event in event handler (imported)`);
};

globalThis.addEventListener("load", handler);
globalThis.addEventListener("beforeunload", handler);
globalThis.addEventListener("unload", handler);

globalThis.onload = (e: Event): void => {
  console.log(`got ${e.type} event in onload function (imported)`);
};

globalThis.onbeforeunload = (e: Event): void => {
  console.log(`got ${e.type} event in onbeforeunload function (imported)`);
};

globalThis.onunload = (e: Event): void => {
  console.log(`got ${e.type} event in onunload function (imported)`);
};

console.log("log from imported script");

关于此示例的几点说明

  • `addEventListener` 和 `onload`/`onunload` 都以 `globalThis` 为前缀,但你也可以使用 `self` 或根本不使用前缀。不建议使用 `window` 作为前缀
  • 你可以使用 `addEventListener` 和/或 `onload`/`onunload` 来定义事件的处理程序。它们之间有一个主要区别,让我们运行示例
$ deno run main.ts
log from imported script
log from main script
got load event in event handler (imported)
got load event in event handler (main)
got load event in onload function (main)
got onbeforeunload event in event handler (imported)
got onbeforeunload event in event handler (main)
got onbeforeunload event in onbeforeunload function (main)
got unload event in event handler (imported)
got unload event in event handler (main)
got unload event in onunload function (main)

使用 `addEventListener` 添加的所有侦听器都已运行,但是 `main.ts` 中定义的 `onload`、`onbeforeunload` 和 `onunload` 覆盖了 `imported.ts` 中定义的处理程序。

换句话说,你可以使用 `addEventListener` 注册多个 `"load"` 或 `"unload"` 事件处理程序,但只会执行最后定义的 `onload`、`onbeforeunload`、`onunload` 事件处理程序。因此,在可能的情况下,最好使用 `addEventListener`。

beforeunload 跳转到标题

// beforeunload.js
let count = 0;

console.log(count);

globalThis.addEventListener("beforeunload", (e) => {
  console.log("About to exit...");
  if (count < 4) {
    e.preventDefault();
    console.log("Scheduling more work...");
    setTimeout(() => {
      console.log(count);
    }, 100);
  }

  count++;
});

globalThis.addEventListener("unload", (e) => {
  console.log("Exiting");
});

count++;
console.log(count);

setTimeout(() => {
  count++;
  console.log(count);
}, 100);

运行此程序将打印

$ deno run beforeunload.js
0
1
2
About to exit...
Scheduling more work...
3
About to exit...
Scheduling more work...
4
About to exit...
Exiting

unhandledrejection 事件 跳转到标题

当没有拒绝处理程序的 Promise 被拒绝时触发此事件,即没有 .catch() 处理程序或 .then() 的第二个参数的 Promise。

// unhandledrejection.js
globalThis.addEventListener("unhandledrejection", (e) => {
  console.log("unhandled rejection at:", e.promise, "reason:", e.reason);
  e.preventDefault();
});

function Foo() {
  this.bar = Promise.reject(new Error("bar not available"));
}

new Foo();
Promise.reject();

运行此程序将打印

$ deno run unhandledrejection.js
unhandled rejection at: Promise {
  <rejected> Error: bar not available
    at new Foo (file:///dev/unhandled_rejection.js:7:29)
    at file:///dev/unhandled_rejection.js:10:1
} reason: Error: bar not available
    at new Foo (file:///dev/unhandled_rejection.js:7:29)
    at file:///dev/unhandled_rejection.js:10:1
unhandled rejection at: Promise { <rejected> undefined } reason: undefined

你找到你需要的内容了吗?

隐私政策