Wretch — 用不可变配置链组织 Fetch
已复核Wretch 是一个围绕 Fetch 构建的 fluent HTTP 客户端。日常类比:先在一张新运单上逐步追加 URL、headers 与 body,调用 .get() / .post() 后才交给运输系统,再用 response chain 选择解析或错误处理方式。
你写:
import wretch from "wretch"
const user = await wretch("/api/users/1") .auth("Bearer abc") .get() .json()这一句会创建带 URL 的配置对象、复制并加入 Authorization header、触发 Fetch,再解析 JSON。固定 3.0.9 同时导出 core、addons 与 middlewares;实际 bundle 取决于 import 与构建器,本轮未测量。
不理解 wretch 这种”一步一个方法、改配置不改原对象”的写法,就解释不了下面几件事:
- 为什么配置方法丢弃返回值时,原实例不会被更新
- 为什么
.get()后得到 ResponseChain,而.json()后才得到 Promise - 为什么 wrapper catcher 与 response-chain error handler 是两层 API
- 为什么 retry、dedupe 与 timeout 分别位于 middleware/addon,而不是 core 默认
Wretch 的执行链可以拆成五步:
-
创建配置对象:factory 用
{...core, _url, _options}生成 fresh object,不是 class instance。 -
复制配置:
.url()、.headers()、.auth()、.catcher()等以 object spread 或新 Map 返回副本;不会修改原 wrapper。 -
动词触发 resolver:
.get()/.post()进入fetch(),普通 object body 在 JSON MIME 边界被 stringify,然后创建 resolver。 -
middleware 包裹 Fetch:
reduceRight把 middleware 组装成洋葱链,最先注册的 middleware 位于最外层。 -
ResponseChain 解析或派发错误:非 ok response 形成
WretchError;.res()/.json()等消费结果,response-chain 的.notFound()等可注册本次请求 catcher。
案例 1:把原生 fetch 三行压成一行
Section titled “案例 1:把原生 fetch 三行压成一行”原生 fetch:
const res = await fetch("/api/users/1", { headers: { Authorization: "Bearer abc" } })if (!res.ok) throw new Error(`HTTP ${res.status}`)const user = await res.json()wretch 等价写法:
const user = await wretch("/api/users/1").auth("Bearer abc").get().json().auth(...) 返回新 wrapper;.get() 已发起底层 Fetch;.json() 选择 body parser。非 ok response 会形成包含 response/status/url 的 WretchError。
案例 2:复用一个 base wrapper
Section titled “案例 2:复用一个 base wrapper”const api = wretch("https://api.example.com") .auth(`Bearer ${token}`) .headers({ "X-Trace-Id": traceId })
const me = await api.url("/me").get().json()const post = await api.url("/posts").post({ title: "hi" }).json()const file = await api.url("/upload").body(blob).put().res()逐部分解释:
api保存 base URL + auth + 公共头。- 每次
.url(...)返回新 object,三处调用互不污染。 .res()返回原始 Response;.json()等方法消费 body。
案例 3:装一个 retry middleware
Section titled “案例 3:装一个 retry middleware”import wretch from "wretch"import { retry } from "wretch/middlewares"
const api = wretch().middlewares([ retry({ delayTimer: 500, maxAttempts: 3, retryOnNetworkError: false, skip: (_url, options) => options.method !== "GET" })])
const data = await api.url("/flaky").get().json()maxAttempts 表示最多重试次数,因此总调用可能是 1 + 3。固定默认会重试 5xx、停止于 4xx,network error 默认不重试;middleware 本身不按 HTTP method 判断,示例用 skip 收窄到 GET。
-
混淆两层 catcher API:wrapper 上是
.catcher(404, fn);.get()后的 ResponseChain 用.notFound(fn)或.error(404, fn),没有同名.catcher()。 -
丢弃配置方法返回值:
api.auth(...)不会修改api,必须接住返回的新 object。 -
把 retry middleware 当幂等保护:默认不检查 method;POST 也可能重试 5xx。副作用请求必须用
skip、idempotency key 或等价策略收口。 -
误读
maxAttempts:值 3 表示初次请求之外最多重试 3 次,总调用可达 4;固定默认值是 10,不是 3。 -
把 addon 当 core 默认:timeout 依赖 Abort addon 的
setTimeout(),retry 依赖 middleware;只 import core 不会自动获得这些策略。
适用 vs 不适用场景
Section titled “适用 vs 不适用场景”适用:
- 满足当前 package Node >=22 边界,或具有标准 Fetch 的目标运行时
- 喜欢不可变 fluent config 与显式 ResponseChain
- 希望按需组合 middleware/addon,并愿意自己定义 retry/timeout policy
不适用:
- 项目已经在用 axios 且团队习惯 config object 风格——再换成本不划算
- 用 Nuxt / Nitro——
ofetch框架已经默认集成 - Node 版本低于当前 package engines,且无法升级
- 想要”运行时强校验响应”——wretch 不做,得再加 zod / valibot
固定版本边界
Section titled “固定版本边界”- 本文绑定
elbywan/wretch@32d5f68b...,tag、package 与 npmgitHead均为3.0.9。 - package 同时提供 import/require exports,声明 Node >=22。
- retry middleware 默认
delayTimer=500、线性 delay ramp、maxAttempts=10、停止于 ok 或 4xx、network retry 关闭。 - 本文未安装依赖、运行上游 Node/browser/Bun/Deno 测试或测量 bundle,状态保持
UNVERIFIED。
- 不可变不要求 class——object spread 与新 Map 就能形成可复用配置链。
- 请求触发与响应消费是两个阶段——动词创建 ResponseChain,parser 再返回 Promise。
- 扩展点也有职责边界——middleware 包装 Fetch,addon 扩展 wrapper/response chain。
- retry 默认不是业务安全策略——method、副作用、body 重放与总预算仍需调用方约束。
- 执行
api.auth("Bearer x")但不保存返回值,后续api.get()会带新 header 吗? retry({maxAttempts: 3})最多会调用底层 Fetch 几次?- 默认 retry middleware 遇到 POST 500,会因为 method 是 POST 而自动跳过吗?
检查点:
- 不会;原 wrapper 未变。
- 最多 4 次,包含初次调用和 3 次 retry。
- 不会。默认 condition 看 response,不看 method;需显式
skip或幂等设计。
- 官方文档:elbywan.github.io/wretch(API 全集 + middleware / addon 列表)
- 固定源码:elbywan/wretch —— 本文绑定提交
32d5f68badf7e8f103b734febe680968c6e0f97f - axios —— 老牌 HTTP 客户端,和 wretch 的设计哲学正相反
- ky —— 同属轻量 fetch 包装,API 风格可对照着看