Chapter 04

标准库与 Web API

@std 标准库全览,以及 Deno 对 Web 浏览器 API 的完整实现

@std 标准库

Deno 官方维护的标准库发布在 JSR 上,以 @std/ 命名空间组织,涵盖文件操作、HTTP、加密、测试、格式化等常用功能,所有模块均有完整类型定义和稳定版本号:

包名功能常用导出
@std/path路径操作join, resolve, extname, basename
@std/fs文件系统walk, ensureDir, copy, exists
@std/httpHTTP 工具serveDir, serveFile, STATUS_CODE
@std/crypto加密工具toHashString, crypto
@std/assert断言(测试用)assertEquals, assertThrows
@std/testing测试工具describe, it, beforeAll
@std/datetime日期时间format, parse, dayOfYear
@std/csvCSV 解析parse, stringify
@std/encoding编解码encodeBase64, decodeBase64
@std/collections集合工具groupBy, chunk, distinct

@std/path — 路径操作

import { join, resolve, extname, basename, dirname } from "jsr:@std/path";

// 跨平台路径拼接
const fullPath = join("/home/user", "projects", "app.ts");
// "/home/user/projects/app.ts"

console.log(extname("app.ts"));       // ".ts"
console.log(basename("/foo/bar.ts"));  // "bar.ts"
console.log(dirname("/foo/bar.ts"));   // "/foo"

// 获取当前文件目录(ESM 中 __dirname 不可用)
const __dirname = dirname(new URL(import.meta.url).pathname);

@std/fs — 文件系统操作

import { walk, ensureDir, copy, exists } from "jsr:@std/fs";

// 确保目录存在(不存在则创建)
await ensureDir("./output/reports");

// 检查文件/目录是否存在
if (await exists("./config.json")) {
  console.log("配置文件存在");
}

// 递归遍历目录(walk 返回 AsyncGenerator)
for await (const entry of walk("./src", { exts: [".ts"] })) {
  if (entry.isFile) {
    console.log(entry.path);
  }
}

// 复制文件
await copy("./src/template.html", "./dist/index.html");

Web 标准 API:fetch

Deno 实现了与浏览器完全兼容的 fetch API,包括完整的 RequestResponseHeaders 对象:

// 基本 GET 请求
const res = await fetch("https://api.example.com/data");
const json = await res.json();

// POST 请求,携带 JSON body 和自定义 Headers
const response = await fetch("https://api.example.com/users", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": `Bearer ${token}`,
  },
  body: JSON.stringify({ name: "Alice", email: "alice@example.com" }),
});

if (!response.ok) {
  throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const user = await response.json();

// Request 对象
const req = new Request("https://api.example.com/data", {
  method: "GET",
  headers: new Headers({ "X-Custom": "value" }),
});
const res2 = await fetch(req);

Web Streams API

Deno 完整实现了 WHATWG Web Streams API,支持流式数据处理,无需将大文件全部载入内存:

// 流式下载大文件
const response = await fetch("https://example.com/large-file.zip");

if (response.body) {
  const reader = response.body.getReader();
  let bytesReceived = 0;

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    bytesReceived += value.length;
    console.log(`已接收: ${bytesReceived} 字节`);
  }
}

// 使用 TransformStream 处理流
const { readable, writable } = new TransformStream({
  transform(chunk, controller) {
    // 将每个字节转为大写(假设是文本)
    controller.enqueue(chunk.map((b: number) => b >= 97 && b <= 122 ? b - 32 : b));
  }
});

// 链式管道
response.body?.pipeTo(writable);

Crypto API(SubtleCrypto)

Deno 实现了 Web Crypto API,提供加密、哈希、签名等功能,与浏览器中的 crypto.subtle 完全兼容:

// SHA-256 哈希
async function sha256(message: string): Promise<string> {
  const encoder = new TextEncoder();
  const data = encoder.encode(message);
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
}

console.log(await sha256("Hello Deno"));

// 生成 HMAC 签名
async function signHMAC(key: string, message: string): Promise<string> {
  const enc = new TextEncoder();
  const cryptoKey = await crypto.subtle.importKey(
    "raw", enc.encode(key),
    { name: "HMAC", hash: "SHA-256" },
    false, ["sign"]
  );
  const sig = await crypto.subtle.sign("HMAC", cryptoKey, enc.encode(message));
  return Array.from(new Uint8Array(sig))
    .map(b => b.toString(16).padStart(2, "0")).join("");
}

// 生成加密随机 UUID
const id = crypto.randomUUID();  // "550e8400-e29b-41d4-a716-446655440000"

// 生成安全随机字节
const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);

URL 与 URLSearchParams

// URL 解析
const url = new URL("https://api.example.com/users?page=2&limit=10");
console.log(url.hostname);    // "api.example.com"
console.log(url.pathname);    // "/users"
console.log(url.searchParams.get("page"));   // "2"
console.log(url.searchParams.get("limit"));  // "10"

// 构建查询字符串
const params = new URLSearchParams({
  q: "deno tutorial",
  lang: "zh",
  page: "1",
});
console.log(params.toString());
// "q=deno+tutorial&lang=zh&page=1"

const apiUrl = `https://api.example.com/search?${params}`;

实战:纯 Web API 构建 HTTP 客户端

使用 Deno 原生 Web API 构建一个完整的、有重试逻辑的 HTTP 客户端,无需任何第三方库:

// http-client.ts — 纯 Web API HTTP 客户端

interface RequestOptions {
  method?: string;
  headers?: Record<string, string>;
  body?: unknown;
  retries?: number;
  timeout?: number;  // ms
}

async function request<T>(url: string, opts: RequestOptions = {}): Promise<T> {
  const { method = "GET", headers = {}, body, retries = 3, timeout = 10000 } = opts;

  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeout);

  for (let i = 0; i < retries; i++) {
    try {
      const res = await fetch(url, {
        method,
        headers: { "Content-Type": "application/json", ...headers },
        body: body ? JSON.stringify(body) : undefined,
        signal: controller.signal,
      });

      if (!res.ok) {
        throw new Error(`HTTP ${res.status}: ${res.statusText}`);
      }

      clearTimeout(timer);
      return await res.json() as T;
    } catch (err) {
      if (i === retries - 1) throw err;
      // 指数退避:1s, 2s, 4s...
      await new Promise(r => setTimeout(r, 1000 * 2 ** i));
    }
  }
  throw new Error("Unreachable");
}

// 使用示例
interface User { id: number; name: string; email: string; }

const users = await request<User[]>("https://jsonplaceholder.typicode.com/users");
console.log(users[0].name);

// deno run --allow-net http-client.ts

本章小结:Deno 对 Web API 标准的完整实现是其最大亮点之一。fetch、Streams、Crypto、URL 这些在浏览器中熟悉的 API,在 Deno 中无需任何 polyfill 即可直接使用。这意味着大量前端逻辑可以直接在 Deno 中复用,前后端代码共享成为现实。@std 标准库则提供了文件、路径、CSV 等服务端常用功能,整洁稳定,有完整类型定义。