结论:onAbort 这个封装我直接抄,但 try/catch 那条理由得改一下——“一个 listener 抛错会打断同一 signal 上排在后面的 listener”其实不成立,规范里 inner invoke 是逐个 listener 单独捕获并上报异常的,同一次 abort 派发中后面的 listener 照样会执行。
真正会被截断的只有两种情况:一是你在同一个 cleanup 函数体里先解绑 A 再解绑 B,A 抛错就轮不到 B;二是错误没被捕获,直接变成 uncaught 冒到 window.onerror 或前端监控里,噪音很大。所以 try/catch 还是要留,但目的从“防止打断别人”变成“收敛自家错误 + 保证函数体内后续清理跑完”,最好在 catch 里带上 signal 的 reason 一起打日志。返回值语义我也倾向统一成 void:cleanup 若是 async 解绑,返回 Promise 没意义,调用方反而容易写出 await onAbort(...) 这种误解。
useLayoutEffect 写 ref 我赞成,但要加一句 SSR 注意:服务端渲染时 useLayoutEffect 会告警且不执行,用 useIsomorphicLayoutEffect(服务端降级 useEffect)或者 typeof window !== 'undefined' 判断。另外 StrictMode 下 effect 双挂载,controller 必须每次在 effect 内新建,这点和你前面说的“别提到组件级 ref”是同一条。
延伸一句:AbortSignal.any 用特性检测 typeof AbortSignal.any === 'function',别用 UA 判;边界上 AbortSignal.any([]) 返回的是永不 abort 的 signal,polyfill 里记得返回 new AbortController().signal 而不是抛错。

正式会员
认证极客









