Logo of ofetch

ofetch

一个更好的 fetch API。在 Node、浏览器和 Worker 中均可使用。

一个更好的 fetch API。在 Node、浏览器和 Worker 中均可使用。

🚀 快速开始

安装

# npm
npm i ofetch

# yarn
yarn add ofetch

导入

// ESM / Typescript
import { ofetch } from "ofetch";

// CommonJS
const { ofetch } = require("ofetch");

✔️ 兼容 Node.js

我们使用 条件导出 来检测 Node.js 并自动使用 unjs/node-fetch-native。如果 globalThis.fetch 可用,则会优先使用它。要利用 Node.js 17.5.0 的实验性原生 fetch API,请使用 --experimental-fetch 标志

keepAlive 支持

通过将 FETCH_KEEP_ALIVE 环境变量设置为 true,将注册一个 HTTP/HTTPS 代理,即使没有待处理的请求,它也会保持套接字连接,以便未来的请求可以直接使用,而无需重新建立 TCP 连接。

注意: 此选项可能导致内存泄漏。请查阅 node-fetch/node-fetch#1325

✔️ 解析响应

ofetch 将使用 destr 智能解析 JSON 和原生值,如果解析失败则回退到文本。

const { users } = await ofetch("/api/users");

对于二进制内容类型,ofetch 将返回一个 Blob 对象。

您可以选择提供一个不同于 destr 的解析器,或者指定 blobarrayBuffertext 以强制使用相应的 FetchResponse 方法解析正文。

// Use JSON.parse
await ofetch("/movie?lang=en", { parseResponse: JSON.parse });

// Return text as is
await ofetch("/movie?lang=en", { parseResponse: (txt) => txt });

// Get the blob version of the response
await ofetch("/api/generate-image", { responseType: "blob" });

✔️ JSON 正文

如果向 body 选项传入一个对象或具有 .toJSON() 方法的类,ofetch 会自动将其字符串化。

ofetch 利用 JSON.stringify() 转换传入的对象。没有 .toJSON() 方法的类必须在传入 body 选项之前提前转换为字符串值。

对于 PUTPATCHPOST 请求方法,当设置字符串或对象正文时,ofetch 会添加默认的 content-type: "application/json"accept: "application/json" 头部(您可以随时覆盖它们)。

此外,ofetch 支持使用 BufferReadableStreamStream兼容的正文类型 的二进制响应。ofetch 将自动设置 duplex: "half" 选项以支持流式传输!

示例

const { users } = await ofetch("/api/users", {
  method: "POST",
  body: { some: "json" },
});

✔️ 错误处理

response.okfalse 时,ofetch 会自动抛出带有友好错误消息和简洁堆栈(隐藏内部细节)的错误。

解析后的错误正文可通过 error.data 获取。您也可以使用 FetchError 类型。

await ofetch("https://google.com/404");
// FetchError: [GET] "https://google/404": 404 Not Found
//     at async main (/project/playground.ts:4:3)

捕获错误响应

await ofetch("/url").catch((err) => err.data);

要绕过状态错误捕获,您可以设置 ignoreResponseError 选项

await ofetch("/url", { ignoreResponseError: true });

✔️ 自动重试

如果发生错误且响应状态码包含在 retryStatusCodes 列表中,ofetch 会自动重试请求。

重试状态码

  • 408 - 请求超时
  • 409 - 冲突
  • 425 - 过早
  • 429 - 请求过多
  • 500 - 内部服务器错误
  • 502 - 错误网关
  • 503 - 服务不可用
  • 504 - 网关超时

您可以使用 retryretryDelay 选项来指定重试次数和重试之间的延迟,也可以使用 retryStatusCodes 选项传入自定义代码数组。

retry 的默认值为 1 次重试,但对于 POSTPUTPATCHDELETE 方法,ofetch 默认不重试以避免引入副作用。如果您为 retry 设置了自定义值,则对于所有请求都将始终重试

retryDelay 的默认值为 0 毫秒。

await ofetch("http://google.com/404", {
  retry: 3,
  retryDelay: 500, // ms
});

✔️ 超时

您可以指定以毫秒为单位的 timeout,以便在超时后自动中止请求(默认禁用)。

await ofetch("http://google.com/404", {
  timeout: 3000, // Timeout after 3 seconds
});

✔️ 类型友好

响应可以获得类型辅助

const article = await ofetch<Article>(`/api/article/${id}`);
// Auto complete working with article.id

✔️ 添加 baseURL

通过使用 baseURL 选项,ofetch 会使用 ufo 自动为其预置斜杠和查询搜索参数。

await ofetch("/config", { baseURL });

✔️ 添加查询搜索参数

通过使用 query 选项(或 params 作为别名),ofetch 会使用 ufo 在保留请求本身查询的情况下,将查询搜索参数添加到 URL 中。

await ofetch("/movie?lang=en", { query: { id: 123 } });

✔️ 拦截器

可以提供异步拦截器来 Hook ofetch 调用的生命周期事件。

您可能希望使用 ofetch.create 来设置共享拦截器。

onRequest({ request, options })

ofetch 被调用时,onRequest 会立即被调用,允许您修改选项或进行简单的日志记录。

await ofetch("/api", {
  async onRequest({ request, options }) {
    // Log request
    console.log("[fetch request]", request, options);

    // Add `?t=1640125211170` to query search params
    options.query = options.query || {};
    options.query.t = new Date();
  },
});

onRequestError({ request, options, error })

当 fetch 请求失败时,onRequestError 将被调用。

await ofetch("/api", {
  async onRequestError({ request, options, error }) {
    // Log error
    console.log("[fetch request error]", request, error);
  },
});

onResponse({ request, options, response })

onResponse 将在 fetch 调用和解析正文后被调用。

await ofetch("/api", {
  async onResponse({ request, response, options }) {
    // Log response
    console.log("[fetch response]", request, response.status, response.body);
  },
});

onResponseError({ request, options, response })

onResponseErroronResponse 相同,但当 fetch 发生且 response.ok 不为 true 时将被调用。

await ofetch("/api", {
  async onResponseError({ request, response, options }) {
    // Log error
    console.log(
      "[fetch response error]",
      request,
      response.status,
      response.body
    );
  },
});

✔️ 使用默认选项创建 fetch

如果您需要在多个 fetch 调用中使用通用选项,此实用程序非常有用。

注意: 默认值将进行一级克隆并继承。请注意像 headers 这样的嵌套选项。

const apiFetch = ofetch.create({ baseURL: "/api" });

apiFetch("/test"); // Same as ofetch('/test', { baseURL: '/api' })

💡 添加头部

通过使用 headers 选项,ofetch 会在请求的默认头部基础上添加额外的头部

await ofetch("/movies", {
  headers: {
    Accept: "application/json",
    "Cache-Control": "no-cache",
  },
});

💡 添加 HTTP(S) Agent

如果您需要使用 HTTP(S) Agent,可以添加 agent 选项并配合 https-proxy-agent(仅适用于 Node.js)

import { HttpsProxyAgent } from "https-proxy-agent";

await ofetch("/api", {
  agent: new HttpsProxyAgent("http://example.com"),
});

🍣 访问原始响应

如果您需要访问原始响应(例如获取头部),可以使用 ofetch.raw

const response = await ofetch.raw("/sushi");

// response._data
// response.headers
// ...

原生 fetch

作为快捷方式,您可以使用 ofetch.native,它提供了原生的 fetch API

const json = await ofetch.native("/sushi").then((r) => r.json());

📦 打包器注意事项

  • 所有目标都以 Module 和 CommonJS 格式以及命名导出方式导出
  • 为了支持现代语法,没有导出被转译
    • 您可能需要使用 Babel 转译 ofetchdestrufo 包以支持 ES5
  • 您需要为支持旧版浏览器(例如使用 unfetch)填充 fetch 全局变量

❓ 常见问题

为什么导出名为 ofetch 而不是 fetch

使用与 fetch 相同的名称可能会引起混淆,因为 API 不同,但它仍然是一个 fetch,所以使用了最接近的替代方案。但是,您可以从 ofetch 中导入 { fetch },它会自动为 Node.js 填充,否则使用原生 fetch。

为什么没有默认导出?

默认导出与 CommonJS 导出混合使用总是存在风险。

这也保证了我们可以在不破坏包的情况下引入更多实用工具,并鼓励使用 ofetch 名称。

为什么不转译?

通过转译库,我们正在用遗留代码阻碍 Web 的发展,而这些代码对大多数用户来说是不必要的。

如果您需要支持旧版用户,可以选择在构建流程中转译该库。

许可

MIT。倾情打造 💖