tRPC 取消调用完整指南:用 AbortSignal/AbortController 中止 Procedure 请求(vanilla 客户端篇) tRPC 取消调用完整指南用 AbortSignal/AbortController 中止 Procedure 请求vanilla 客户端篇【免费下载链接】trpc‍♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc导读本文围绕 tRPC v10 官方文档《Aborting Procedure Calls》展开系统讲解如何在纯 TypeScript/JavaScriptvanilla客户端中取消一个正在进行的 query 或 mutation。tRPC 完全遵循 Web 平台通用的AbortController/AbortSignal标准本文会带你掌握三步式取消套路并结合当前仓库源码解析 signal 从调用方一路穿透到fetch的完整实现链路让你既能写对代码也能讲清原理。结论先行tRPC 的取消就是 Web 标准本身tRPC 在处理“中止abort过程调用”时没有发明任何私有协议而是直接采纳行业标准你只需要把AbortSignal作为第三个参数传给 query 或 mutation 的调用在需要取消的时刻调用该AbortController实例的abort()方法。其余事情——HTTP 连接中断、Promise 以AbortError拒绝、资源释放——全部交给标准机制与 tRPC 客户端内部链路处理。正如官方文档见 v10 版 aborting-procedures.md以及当前主分支维护的同名新文档 www/docs/client/vanilla/aborting-procedures.md所强调的AbortController 是“a standard javascript API”因此你在 tRPC 之外学到的所有取消模式在这里同样适用。三步取消一个 query/mutation完整可运行示例以下示例完整取自官方文档并保留其逐步注释演示 vanilla 客户端中如何创建控制器、把 signal 挂到调用上、以及随时取消import { createTRPCProxyClient, httpBatchLink } from trpc/client; import type { AppRouter } from ./server; const proxy createTRPCProxyClientAppRouter({ links: [ httpBatchLink({ url: http://localhost:3000/trpc, }), ], }); // 1. 创建一个 AbortController 实例 —— 这是标准的 JavaScript API const ac new AbortController(); // 2. 把 signal 传给 query或 mutation const query proxy.userById.query(id_bilbo, { signal: ac.signal }); // 3. 在需要时取消这次请求 ac.abort();要点拆解客户端创建vanilla 场景使用createTRPCProxyClientAppRouter当前仓库主分支新客户端 API 中它被createTRPCClient别名化用法一致链路links上挂的是httpBatchLink说明取消能力对批量 HTTP 传输同样有效。参数位置第二个参数是输入id_bilbo第三个可选参数是配置对象其中携带signal字段。取消时机ac.abort()可以在发起调用后的任意时刻执行无论是请求尚未发出、已发出还在等待响应都会触发取消。与文档配套的还有 vanilla 客户端引入指南 setup.mdx 与 overview本文不再展开客户端初始化细节。请求被取消后会发生什么当abort()被调用时你在第 2 步拿到的queryPromise不会以正常结果 resolve而是以一个取消错误拒绝。仓库中的回归测试精确锁定了这一行为见 packages/tests/server/abortQuery.test.tsdescribe(vanilla client procedure abortion, () { test(query, async () { const abortController new AbortController(); const signal abortController.signal; await using ctx testServerAndClientResource(router); const promise ctx.client.testQuery.query(undefined, { signal }); abortController.abort(); await expect(promise).rejects.toThrowError(/aborted/); }); test(mutation, async () { // ……结构同上mutate 同样会被取消 }); });该测试分别覆盖了 query 与 mutation 两类过程发起调用拿到 Promise 后立刻abort()随后断言 Promise 以包含aborted语义的错误拒绝。这印证了取消并非“忽略响应”那么简单——客户端链路确实把中止事件转化为了一次确定的失败从而让你的catch/finally逻辑可以可靠地处理“用户主动取消”这一分支。错误为什么会“长得像 AbortError”从实现角度解释这个错误形态请求层遵循 fetch 规范中止时抛出的错误名称name为AbortError。为了在不同运行环境下保持一致的语义httpUtils.ts 中内置了一套逐级降级的兜底实现优先调用原生signal.throwIfAborted()若环境提供DOMException则抛new DOMException(AbortError, AbortError)否则回退到内部自定义的AbortError extends Error。随后在 TRPCUntypedClient.ts 中这类错误会经过TRPCClientError.from(...)统一包装成TRPCClientError抛出因此业务侧用统一的客户端错误处理逻辑即可捕获取消场景。取消能力为何能覆盖不同链路从 signal 到 fetch 的调用链把signal传进 options 只是第一步理解它“怎么生效”才是关键。梳理当前仓库客户端源码signal 的传播路径是一条清晰的责任链类型入口客户端统一的可选参数类型TRPCRequestOptions明确声明了signal?: AbortSignal见 packages/client/src/internals/TRPCUntypedClient.ts。过程分发query()/mutation()/subscription()三个方法都会把opts.signal透传给内部$request再随op对象一并进入createChain(...)组成的链接链。HTTP 链接消费以 HTTP 系链接为例httpLink.ts 处将op.signal原样注入底层请求最终在 fetchHTTPResponse 中这个 signal 被作为fetch(url, { signal })的选项交给真正的网络层——于是“中止 signal”等同于“中止这次 fetch”浏览器的网络栈会立刻中断连接。这一设计带来的直接结论是只要某个 link 在内部使用基于fetch的传输httpLink、httpBatchLink、httpBatchStreamLink均属于此类取消语义就是开箱即用的。正因如此文档才会强调“只需传 signal”而不必关心 link 内部的具体差异。一个值得注意的实现细节请求发出前就已 abort 的情况如果abort()在请求真正发出去之前就发生了上面的调用链依然稳妥fetchHTTPResponse的第一行会先执行throwIfAborted(opts.signal)见 httpUtils.ts在发起网络请求前就立即抛错从而避免一次注定被取消的空请求浪费连接资源。深入仓库signal 组合工具与订阅/流式场景除了单次请求的取消客户端内部还维护着一组面向“多信号组合”的工具函数全部集中在 packages/client/src/internals/signals.ts函数语义典型用途allAbortSignals(...signals)“Promise.all 版”合并——所有传入信号都 abort 后才 abort 结果信号任一信号为 null 则永不自动 abort需要多个前置条件全部释放后才终止的场景raceAbortSignals(...signals)“Promise.race 版”合并——任一信号 abort 即终止是AbortSignal.any的 ponyfill任一路径可触发取消的组合订阅abortSignalToPromise(signal)把一个 signal 转化为一个永不 resolve、仅在 abort 时以signal.reasonreject 的 Promise在 async 流程中等待取消事件这些工具主要服务于订阅subscription与流式传输内部对连接状态的管理。虽然 vanilla 文档的主体是 query/mutation 的一次性调用但了解它们有助于在阅读 订阅指南 或自定义 link 时建立完整认知。取消在 React / Next.js 生态中的对应写法如果你是框架用户无需在组件里手动维护AbortController——各框架适配层已在内部接好同样的机制React 用户可阅读 v10 版 react/aborting-procedures.md当前主分支对应新版见 www/docs/client/react/aborting-procedures.md借助 hook 生命周期自动取消Next.js 用户可阅读 v10 版 nextjs/aborting-procedures.md。它们与本文的 vanilla 方案共享同一条 signal 传播链路——区别只在于AbortController由谁创建、何时调用abort()。实践建议与常见误区结合文档与源码可以提炼出几条可直接落地的经验一个 controller 可服务多个调用AbortSignal可以被任意多个请求复用适合“页面卸载/组件切换时一次性取消一批在途请求”的场景。注意abort()后该 signal 永久处于 aborted 状态如需再次发起新请求应新建AbortController。别把取消当成静默吞掉取消后 Promise 会拒绝rejects务必在catch分支区分“用户取消”与“真实错误”避免把取消场景误报为服务端异常。把取消看成协议而非优化从源码看abort 会穿透到真正的fetch层httpUtils.ts意味着服务端同样能感知到连接中断对长耗时查询尤其建议提供取消入口。Node 环境同样适用AbortController 已是现代 Node.js 的内建全局 API服务端调用createCaller/客户端直连等场景不必额外 polyfill仓库对DOMException缺失等老环境另有兜底见上文错误形态一节。小结tRPC 把“取消”这件事做到了最简三步代码创建 controller → 传入 signal → 调用 abort()即可完成一个生产可用的取消流程而其背后是从类型定义TRPCRequestOptions.signal出发、经由链接链、最终落到fetch(url, { signal })的标准 Web 语义。无论你使用的是 httpBatchLink 还是其他 HTTP 系链接理解这条调用链后你都能自信地在自己的应用中写出可预期、可测试的取消逻辑。【免费下载链接】trpc‍♀️ Move Fast and Break Nothing. End-to-end typesafe APIs made easy.项目地址: https://gitcode.com/GitHub_Trending/tr/trpc创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考