闭包实现自动取消的可观测异步流包装器需三点:状态隔离(封装signal、计时器、指标等)、信号监听(透传AbortSignal并手动轮询aborted)、生命周期联动(统一触发onStarted/onCancelled等钩子并清理资源)。

直接用闭包实现“自动取消”的可观测异步流包装器,核心在于三点:状态隔离、信号监听、生命周期联动。它不是给 Promise 加个 .cancel() 就完事,而是让整个异步流程能响应外部中断、记录关键节点、并在终止时释放资源或上报指标。
封装取消信号与可观测上下文
闭包天然适合把 signal(如 AbortSignal)、计时器 ID、请求 ID、开始时间、重试次数等打包进同一作用域。这些变量不暴露给外部,但所有内部回调都能安全读写:
- 用 AbortController 创建 signal,并在闭包外保留 controller 引用,便于主动 abort()
- 在闭包内初始化 startTime = Date.now()、attempt = 0、metrics = { started: 0, cancelled: 0, resolved: 0, rejected: 0 }
- 将 signal 透传给 fetch 或其他支持 signal 的原生 API;对不支持的逻辑(如 setTimeout、自定义轮询),手动轮询 signal.aborted
注入可观测钩子(hooks)
可观测性 ≠ 打 console.log。真正的可观测需要结构化埋点,而闭包是挂载这些钩子最轻量的方式:
- 提供 onStarted、onCancelled、onResolved、onRejected 四个可选回调,由调用方传入,闭包内部统一触发
- 每个钩子接收统一上下文对象:{ id, attempt, elapsed: Date.now() - startTime, error?, value? }
- 例如在 fetch.then 前调用 onStarted(ctx),在 signal.aborted 为 true 时立即调用 onCancelled(ctx),避免等待超时
组合取消与重试逻辑
自动取消常和重试共存——比如“最多重试 2 次,总耗时不超过 8 秒”,这时需让 abort 和 retry 协同工作:
- 每次重试前检查 signal.aborted,若已中止则跳过本次尝试并直接 reject
- 用 setTimeout 控制总超时:在闭包顶层启动一个 timer,abort() 时 clearTimeout,resolve/reject 时也 clearTimeout
- 把重试延迟(如指数退避)和取消信号绑定:delay = Math.min(baseDelay * 2^attempt, remainingTime),防止最后 100ms 还发起无意义请求
返回带语义的可取消 Promise
最终返回的 Promise 不仅要可 cancel,还要自带可观测元数据:
- Promise 实例上挂载 .abort() 方法(调 controller.abort())和 .status() 方法(返回当前 { state: 'pending'|'cancelled'|'resolved'|'rejected', metrics })
- reject 时抛出带 cause 字段的错误对象,例如 new Error('fetch failed') 附加 { cause: 'network', signalAborted: true, attempt: 2 }
- 确保 .finally() 或外部 .catch() 不会干扰内部钩子执行顺序——所有可观测动作都在 Promise 构造器内完成,不依赖链式 then
不复杂但容易忽略:可观测性真正起效的前提,是取消动作发生时,所有中间状态(如 pending 的定时器、未 resolve 的 Promise、正在传输的流)都被显式清理。闭包帮你锁住这些引用,但得你亲手写 clearTimeout、controller.abort()、stream.cancel() —— 它们不是自动发生的。

















