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 的信息

HTTP 服务器的实际示例

权限 跳到标题

权限是在运行 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。这些函数调用将在专用阻塞线程上运行,并返回解析为所需 resultPromise

使用 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(对于指针,为 null 指针)作为结果。

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 类型已拆分为 pointerbuffer 类型,以确保用户可以利用 Typed Array 的优化;自 Deno 1.31 起,pointer 的 JavaScript 表示已成为不透明的指针对象,对于空指针则为 null

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

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");

关于此示例的几点说明

  • addEventListeneronload/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 中定义的 onloadonbeforeunloadonunload 覆盖了 imported.ts 中定义的处理程序。

换句话说,你可以使用 addEventListener 注册多个 "load""unload" 事件处理程序,但只有最后定义的 onloadonbeforeunloadonunload 事件处理程序会被执行。因此,在可能的情况下,首选使用 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

您找到所需内容了吗?

隐私政策