Bun 网络请求 #

概述 #

Bun 内置了 Web 标准的 fetch API 和 WebSocket 支持,无需安装 node-fetch 或 ws 等库即可进行网络请求和实时通信。

fetch API #

基本用法 #

typescript
// GET 请求
const response = await fetch("https://api.example.com/data");
const data = await response.json();
console.log(data);

// 获取文本
const text = await response.text();

// 获取 ArrayBuffer
const buffer = await response.arrayBuffer();

// 获取 Blob
const blob = await response.blob();

请求配置 #

typescript
const response = await fetch("https://api.example.com/users", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": "Bearer token123",
  },
  body: JSON.stringify({
    name: "Bun",
    email: "bun@example.com",
  }),
});

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

处理响应 #

typescript
const response = await fetch("https://api.example.com/data");

// 状态码
console.log(response.status);      // 200
console.log(response.ok);          // true
console.log(response.statusText);  // "OK"

// 响应头
console.log(response.headers.get("content-type"));
console.log([...response.headers]);

// 读取内容
const json = await response.json();
const text = await response.text();
const buffer = await response.arrayBuffer();
const blob = await response.blob();

错误处理 #

typescript
try {
  const response = await fetch("https://api.example.com/data");
  
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  
  const data = await response.json();
  console.log(data);
} catch (error) {
  if (error instanceof TypeError) {
    console.error("Network error:", error.message);
  } else {
    console.error("Error:", error);
  }
}

超时控制 #

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

try {
  const response = await fetch("https://api.example.com/data", {
    signal: controller.signal,
  });
  const data = await response.json();
  console.log(data);
} catch (error) {
  if (error.name === "AbortError") {
    console.error("Request timed out");
  }
} finally {
  clearTimeout(timeout);
}

取消请求 #

typescript
const controller = new AbortController();

// 发起请求
const fetchPromise = fetch("https://api.example.com/data", {
  signal: controller.signal,
});

// 取消请求
controller.abort();

try {
  const response = await fetchPromise;
} catch (error) {
  if (error.name === "AbortError") {
    console.log("Request was cancelled");
  }
}

Request 对象 #

创建 Request #

typescript
const request = new Request("https://api.example.com/users", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ name: "Bun" }),
});

const response = await fetch(request);

Request 属性 #

typescript
const request = new Request("https://api.example.com/data?q=test", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ key: "value" }),
});

console.log(request.url);       // 完整 URL
console.log(request.method);    // "POST"
console.log(request.headers);   // Headers 对象
console.log(request.body);      // ReadableStream

// 读取 body
const body = await request.json();
console.log(body);

克隆 Request #

typescript
const request = new Request("https://api.example.com/data", {
  body: JSON.stringify({ key: "value" }),
});

// 克隆请求
const clonedRequest = request.clone();

Response 对象 #

创建 Response #

typescript
// 基本响应
const response = new Response("Hello, World!", {
  status: 200,
  headers: { "Content-Type": "text/plain" },
});

// JSON 响应
const jsonResponse = Response.json({ message: "Hello" });

// 重定向
const redirectResponse = Response.redirect("https://example.com", 302);

// 错误响应
const errorResponse = Response.error();

Response 方法 #

typescript
const response = new Response("Hello");

// 克隆
const cloned = response.clone();

// 读取内容
const text = await response.text();
const json = await response.json();
const buffer = await response.arrayBuffer();
const blob = await response.blob();

Headers 对象 #

创建 Headers #

typescript
// 从对象创建
const headers = new Headers({
  "Content-Type": "application/json",
  "Authorization": "Bearer token",
});

// 添加头部
headers.set("X-Custom", "value");
headers.append("X-Custom", "another");

// 获取头部
console.log(headers.get("Content-Type"));
console.log(headers.get("X-Custom"));  // "value, another"

// 检查存在
console.log(headers.has("Authorization"));

// 删除头部
headers.delete("X-Custom");

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

常见请求场景 #

GET 请求 #

typescript
// 简单 GET
const data = await fetch("https://api.example.com/data").then(r => r.json());

// 带查询参数
const url = new URL("https://api.example.com/search");
url.searchParams.set("q", "bun");
url.searchParams.set("limit", "10");

const results = await fetch(url).then(r => r.json());

POST 请求 #

typescript
// JSON 数据
const response = await fetch("https://api.example.com/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Bun", email: "bun@example.com" }),
});

// 表单数据
const formData = new FormData();
formData.append("name", "Bun");
formData.append("file", new Blob(["content"]), "file.txt");

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

// URL 编码
const params = new URLSearchParams();
params.append("name", "Bun");
params.append("email", "bun@example.com");

const response = await fetch("https://api.example.com/users", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: params,
});

文件上传 #

typescript
// 上传文件
const file = Bun.file("./document.pdf");

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

// 上传多个文件
const formData = new FormData();
formData.append("file1", Bun.file("./file1.txt"));
formData.append("file2", Bun.file("./file2.txt"));

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

文件下载 #

typescript
// 下载文件
const response = await fetch("https://example.com/file.pdf");
const blob = await response.blob();

// 保存到本地
await Bun.write("./downloaded.pdf", blob);

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

if (stream) {
  const file = Bun.file("./large-file.zip");
  await Bun.write("./large-file.zip", stream);
}

认证请求 #

typescript
// Basic Auth
const credentials = btoa("username:password");
const response = await fetch("https://api.example.com/protected", {
  headers: {
    "Authorization": `Basic ${credentials}`,
  },
});

// Bearer Token
const response = await fetch("https://api.example.com/protected", {
  headers: {
    "Authorization": "Bearer your-token-here",
  },
});

// API Key
const response = await fetch("https://api.example.com/data", {
  headers: {
    "X-API-Key": "your-api-key",
  },
});
typescript
// 发送 Cookie
const response = await fetch("https://api.example.com/data", {
  credentials: "include",  // 包含 Cookie
  headers: {
    "Cookie": "session=abc123",
  },
});

// 获取 Set-Cookie
const setCookie = response.headers.get("set-cookie");

WebSocket #

客户端 WebSocket #

typescript
const ws = new WebSocket("wss://example.com/socket");

ws.onopen = () => {
  console.log("Connected");
  ws.send("Hello, Server!");
};

ws.onmessage = (event) => {
  console.log("Received:", event.data);
};

ws.onerror = (error) => {
  console.error("Error:", error);
};

ws.onclose = (event) => {
  console.log("Disconnected", event.code, event.reason);
};

// 发送消息
ws.send("Hello");
ws.send(JSON.stringify({ type: "ping" }));

// 发送二进制数据
const buffer = new Uint8Array([1, 2, 3, 4]);
ws.send(buffer);

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

WebSocket 连接选项 #

typescript
const ws = new WebSocket("wss://example.com/socket", {
  headers: {
    "Authorization": "Bearer token",
  },
  protocols: ["chat", "superchat"],
});

心跳保活 #

typescript
const ws = new WebSocket("wss://example.com/socket");

let pingInterval: Timer;

ws.onopen = () => {
  pingInterval = setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({ type: "ping" }));
    }
  }, 30000);
};

ws.onclose = () => {
  clearInterval(pingInterval);
};

自动重连 #

typescript
class ReconnectingWebSocket {
  private ws: WebSocket | null = null;
  private url: string;
  private reconnectAttempts = 0;
  private maxReconnectAttempts = 5;
  private reconnectDelay = 1000;

  constructor(url: string) {
    this.url = url;
    this.connect();
  }

  private connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      console.log("Connected");
      this.reconnectAttempts = 0;
    };

    this.ws.onclose = () => {
      console.log("Disconnected");
      this.reconnect();
    };

    this.ws.onerror = (error) => {
      console.error("Error:", error);
    };
  }

  private reconnect() {
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++;
      const delay = this.reconnectDelay * this.reconnectAttempts;
      console.log(`Reconnecting in ${delay}ms...`);
      setTimeout(() => this.connect(), delay);
    }
  }

  send(data: string) {
    this.ws?.send(data);
  }

  close() {
    this.ws?.close();
  }
}

HTTP 客户端封装 #

请求函数封装 #

typescript
interface RequestOptions extends RequestInit {
  timeout?: number;
  params?: Record<string, string>;
}

async function request<T>(
  url: string,
  options: RequestOptions = {}
): Promise<T> {
  const { timeout = 30000, params, ...init } = options;

  // 添加查询参数
  if (params) {
    const urlObj = new URL(url);
    Object.entries(params).forEach(([key, value]) => {
      urlObj.searchParams.set(key, value);
    });
    url = urlObj.toString();
  }

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

  try {
    const response = await fetch(url, {
      ...init,
      signal: controller.signal,
    });

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

    return response.json();
  } finally {
    clearTimeout(timeoutId);
  }
}

// 使用
const data = await request<{ id: number }>("https://api.example.com/data", {
  params: { page: "1" },
});

API 客户端类 #

typescript
class APIClient {
  private baseURL: string;
  private headers: HeadersInit;

  constructor(baseURL: string, headers: HeadersInit = {}) {
    this.baseURL = baseURL;
    this.headers = headers;
  }

  async get<T>(path: string, params?: Record<string, string>): Promise<T> {
    const url = new URL(path, this.baseURL);
    if (params) {
      Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
    }
    
    const response = await fetch(url, {
      headers: this.headers,
    });
    
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  }

  async post<T>(path: string, body: unknown): Promise<T> {
    const response = await fetch(new URL(path, this.baseURL), {
      method: "POST",
      headers: {
        ...this.headers,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });
    
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  }

  async put<T>(path: string, body: unknown): Promise<T> {
    const response = await fetch(new URL(path, this.baseURL), {
      method: "PUT",
      headers: {
        ...this.headers,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });
    
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  }

  async delete<T>(path: string): Promise<T> {
    const response = await fetch(new URL(path, this.baseURL), {
      method: "DELETE",
      headers: this.headers,
    });
    
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return response.json();
  }
}

// 使用
const api = new APIClient("https://api.example.com", {
  "Authorization": "Bearer token",
});

const users = await api.get<User[]>("/users");
const user = await api.post<User>("/users", { name: "Bun" });

下一步 #

现在你已经了解了 Bun 的网络请求功能,接下来学习 进程与子进程 深入了解 Bun 的进程管理。

最后更新:2026-03-29