强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 08 章:Web API

第 08 章:Web API

8.1 Web 标准 API 概述

Deno 内置了大量 WHATWG/W3C Web 标准 API,这意味着你在浏览器中使用过的 API 大多可以直接在 Deno 中使用。

内置 Web API 一览

API说明浏览器兼容
fetchHTTP 请求
Request / ResponseHTTP 请求/响应对象
HeadersHTTP 头部操作
URL / URLSearchParamsURL 解析
FormData表单数据
Blob二进制大对象
File文件对象
WebSocketWebSocket 客户端
TextEncoder / TextDecoder文本编码
AbortController请求取消
ReadableStream / WritableStream流 API
crypto加密 API
structuredClone深拷贝
console控制台输出
setTimeout / setInterval定时器
queueMicrotask微任务

8.2 fetch — HTTP 请求

基本 GET 请求

// GET 请求
const response = await fetch("https://api.github.com/users/denoland");

// 检查状态码
if (!response.ok) {
  throw new Error(`HTTP Error: ${response.status}`);
}

// 解析 JSON
const user = await response.json();
console.log(user.login);
console.log(user.public_repos);

// 获取响应头
console.log("Content-Type:", response.headers.get("content-type"));
console.log("Rate Limit:", response.headers.get("x-ratelimit-remaining"));

POST 请求

// 发送 JSON 数据
const response = await fetch("https://httpbin.org/post", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer token123",
  },
  body: JSON.stringify({
    name: "Alice",
    email: "[email protected]",
  }),
});

const result = await response.json();
console.log(result);

请求超时控制

// 使用 AbortController 控制超时
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 秒超时

try {
  const response = await fetch("https://api.example.com/data", {
    signal: controller.signal,
  });
  clearTimeout(timeoutId);
  const data = await response.json();
  console.log(data);
} catch (error) {
  if (error.name === "AbortError") {
    console.error("请求超时");
  }
}

流式响应

// 流式读取大文件
const response = await fetch("https://example.com/large-file.bin");

// 方法 1:使用 arrayBuffer
const buffer = await response.arrayBuffer();
await Deno.writeFile("file.bin", new Uint8Array(buffer));

// 方法 2:使用流(内存友好)
const reader = response.body?.getReader();
const file = await Deno.open("file.bin", { write: true, create: true });

while (true) {
  const { done, value } = await reader!.read();
  if (done) break;
  await file.write(value);
}
file.close();

上传文件

// 上传文件
const fileData = await Deno.readFile("photo.jpg");

const formData = new FormData();
formData.append("file", new Blob([fileData]), "photo.jpg");
formData.append("description", "我的照片");

const response = await fetch("https://api.example.com/upload", {
  method: "POST",
  body: formData,
});

console.log("上传结果:", await response.json());

8.3 WebSocket — 实时通信

WebSocket 客户端

// 连接 WebSocket 服务器
const ws = new WebSocket("ws://localhost:8080/ws");

ws.onopen = () => {
  console.log("已连接");
  ws.send(JSON.stringify({ type: "greeting", message: "Hello!" }));
};

ws.onmessage = (event) => {
  console.log("收到消息:", event.data);
};

ws.onerror = (error) => {
  console.error("WebSocket 错误:", error);
};

ws.onclose = () => {
  console.log("连接已关闭");
};

使用事件监听方式

const ws = new WebSocket("ws://localhost:8080/ws");

ws.addEventListener("open", () => {
  console.log("连接建立");
});

ws.addEventListener("message", (event) => {
  if (typeof event.data === "string") {
    console.log("文本消息:", event.data);
  } else {
    console.log("二进制消息:", event.data);
  }
});

ws.addEventListener("close", (event) => {
  console.log(`连接关闭:code=${event.code}, reason=${event.reason}`);
});

// 发送消息
ws.send("Hello Server");

// 关闭连接
ws.close(1000, "正常关闭");

8.4 URL 与 URLSearchParams

URL 解析

const url = new URL("https://example.com:8080/path?query=value&key=123#section");

console.log(url.protocol);  // https:
console.log(url.hostname);  // example.com
console.log(url.port);      // 8080
console.log(url.pathname);  // /path
console.log(url.search);    // ?query=value&key=123
console.log(url.hash);      // #section
console.log(url.origin);    // https://example.com:8080

URLSearchParams

// 从字符串创建
const params = new URLSearchParams("?name=Alice&age=30&city=北京");

console.log(params.get("name"));  // Alice
console.log(params.get("age"));   // 30
console.log(params.has("email")); // false

// 添加参数
params.append("email", "[email protected]");

// 删除参数
params.delete("age");

// 遍历参数
for (const [key, value] of params) {
  console.log(`${key}: ${value}`);
}

// 转为字符串
console.log(params.toString());  // name=Alice&city=%E5%8C%97%E4%BA%AC&email=alice%40example.com

构建 URL

// 动态构建 API URL
function buildApiUrl(base: string, path: string, params: Record<string, string>): string {
  const url = new URL(path, base);
  for (const [key, value] of Object.entries(params)) {
    url.searchParams.set(key, value);
  }
  return url.toString();
}

const apiUrl = buildApiUrl("https://api.example.com", "/v1/users", {
  page: "1",
  limit: "10",
  sort: "name",
});
// https://api.example.com/v1/users?page=1&limit=10&sort=name

8.5 FormData

// 创建 FormData
const form = new FormData();
form.append("username", "alice");
form.append("email", "[email protected]");

// 添加文件
const fileContent = await Deno.readFile("document.pdf");
form.append("document", new Blob([fileContent], { type: "application/pdf" }), "document.pdf");

// 遍历表单数据
for (const [key, value] of form.entries()) {
  if (value instanceof File) {
    console.log(`${key}: 文件=${value.name}, 大小=${value.size}`);
  } else {
    console.log(`${key}: ${value}`);
  }
}

// 发送表单数据
const response = await fetch("https://api.example.com/register", {
  method: "POST",
  body: form,
});

8.6 Blob 与 File

// 创建 Blob
const textBlob = new Blob(["Hello, World!"], { type: "text/plain" });
const jsonBlob = new Blob([JSON.stringify({ key: "value" })], { type: "application/json" });

// 读取 Blob 内容
const text = await textBlob.text();
console.log(text);  // Hello, World!

const arrayBuffer = await jsonBlob.arrayBuffer();
console.log(arrayBuffer.byteLength);

// 创建 File
const file = new File(["文件内容"], "test.txt", {
  type: "text/plain",
  lastModified: Date.now(),
});

console.log(file.name);         // test.txt
console.log(file.size);         // 文件大小
console.log(file.type);         // text/plain
console.log(file.lastModified); // 修改时间

8.7 AbortController

// 场景 1:取消 fetch 请求
const controller = new AbortController();

fetch("https://api.example.com/data", { signal: controller.signal })
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === "AbortError") {
      console.log("请求被取消");
    }
  });

// 3 秒后取消
setTimeout(() => controller.abort(), 3000);

// 场景 2:多个请求共享取消信号
const controller2 = new AbortController();

const promise1 = fetch("/api/users", { signal: controller2.signal });
const promise2 = fetch("/api/posts", { signal: controller2.signal });
const promise3 = fetch("/api/comments", { signal: controller2.signal });

// 任何一个失败就取消全部
Promise.allSettled([promise1, promise2, promise3]).then(() => {
  console.log("所有请求完成");
});

// 场景 3:超时控制
function fetchWithTimeout(url: string, ms: number) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), ms);
  
  return fetch(url, { signal: controller.signal })
    .finally(() => clearTimeout(timeoutId));
}

try {
  const res = await fetchWithTimeout("https://api.example.com", 5000);
  console.log("请求成功", res.status);
} catch {
  console.log("请求超时");
}

8.8 Streams API

ReadableStream

// 创建自定义 ReadableStream
function createCounter(): ReadableStream<number> {
  let count = 0;
  return new ReadableStream({
    pull(controller) {
      if (count < 10) {
        controller.enqueue(count++);
      } else {
        controller.close();
      }
    },
  });
}

// 使用
const stream = createCounter();
const reader = stream.getReader();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  console.log(value);  // 0, 1, 2, ..., 9
}

TransformStream

// 创建转换流:将文本转为大写
const uppercase = new TransformStream<string, string>({
  transform(chunk, controller) {
    controller.enqueue(chunk.toUpperCase());
  },
});

// 使用
const response = await fetch("https://example.com/text");
const readable = response.body!
  .pipeThrough(new TextDecoderStream())
  .pipeThrough(uppercase);

const reader = readable.getReader();
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  console.log(value);
}

流式文件处理

// 流式复制大文件
async function streamCopy(src: string, dest: string) {
  const srcFile = await Deno.open(src, { read: true });
  const destFile = await Deno.open(dest, { write: true, create: true });
  
  await srcFile.readable.pipeTo(destFile.writable);
  // pipeTo 会自动关闭流
}

await streamCopy("large-input.dat", "large-output.dat");

8.9 crypto API

// 生成 UUID
const uuid = crypto.randomUUID();
console.log(uuid);  // 550e8400-e29b-41d4-a716-446655440000

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

// SHA-256 哈希
const data = new TextEncoder().encode("Hello, Deno!");
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
console.log("SHA-256:", hashHex);

8.10 structuredClone — 深拷贝

// 深拷贝对象
const original = {
  name: "Alice",
  nested: { age: 30, hobbies: ["reading", "coding"] },
  date: new Date(),
};

const cloned = structuredClone(original);

cloned.nested.hobbies.push("gaming");
console.log(original.nested.hobbies);  // ["reading", "coding"] - 未被修改
console.log(cloned.nested.hobbies);    // ["reading", "coding", "gaming"]

// 拷贝复杂数据结构
const data = new Map([["key1", { value: 1 }], ["key2", { value: 2 }]]);
const clonedData = structuredClone(data);

8.11 实战场景

场景:API 客户端封装

class ApiClient {
  #baseUrl: string;
  #token?: string;

  constructor(baseUrl: string, token?: string) {
    this.#baseUrl = baseUrl;
    this.#token = token;
  }

  async get<T>(path: string, params?: Record<string, string>): Promise<T> {
    const url = new URL(path, this.#baseUrl);
    if (params) {
      for (const [k, v] of Object.entries(params)) {
        url.searchParams.set(k, v);
      }
    }

    const response = await fetch(url.toString(), {
      headers: {
        ...(this.#token ? { Authorization: `Bearer ${this.#token}` } : {}),
        "Accept": "application/json",
      },
    });

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

    return response.json() as Promise<T>;
  }

  async post<T>(path: string, body: unknown): Promise<T> {
    const response = await fetch(new URL(path, this.#baseUrl).toString(), {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        ...(this.#token ? { Authorization: `Bearer ${this.#token}` } : {}),
      },
      body: JSON.stringify(body),
    });

    if (!response.ok) {
      throw new Error(`API Error: ${response.status}`);
    }

    return response.json() as Promise<T>;
  }
}

// 使用
const api = new ApiClient("https://api.example.com", "my-token");
const users = await api.get<User[]>("/v1/users", { page: "1" });
const newUser = await api.post<User>("/v1/users", { name: "Bob" });

8.12 本章小结

API用途常用场景
fetchHTTP 请求API 调用、数据获取
WebSocket实时通信聊天、实时推送
URL / URLSearchParamsURL 处理路由、参数解析
FormData表单数据文件上传
AbortController请求取消超时控制、组件卸载取消
Streams流处理大文件处理、实时数据
crypto加密哈希、UUID、随机数
structuredClone深拷贝状态管理、数据隔离

📖 扩展阅读


下一章第 09 章:文件 I/O → 深入了解 Deno 的文件读写与目录操作。