Ky 替换 signal 后,父控制器仍会取消请求
派生 Ky 客户端使用 replaceOption(signal),父实例的取消仍生效时,检查信号合并实现。区分替换、组合与请求已取消,保留超时策略。
父 Ky 实例绑定了一个 AbortSignal,派生实例又用 replaceOption() 换成新的 signal。父控制器一取消,派生请求却仍抛 AbortError。这种情况下,应检查信号是否真的替换,不能只看最终配置对象表面上的 signal。
旧实现会先收集参与合并的 AbortSignal,再处理 replaceOption 包装。包着新 signal 的值不是 AbortSignal 实例,旧信号却已留在收集数组中;最后通过 AbortSignal.any() 组合时,父级信号又被带了回来。
普通 signal 合并与显式替换有不同含义
派生实例同时受父级和本次控制器管理,有时正是业务需要。只有明确要求派生客户端脱离父级取消生命周期时,才应替换它。不要为了消除一次 AbortError,全局把多个信号的组合都关掉。
正式 Ky 2.2.0 会先解开 replaceOption,再处理顶层 signal。显式替换或将 signal 设为 undefined 时,会先清空已累计的父级信号。以下示例只创建客户端,没有发请求:
import ky, { replaceOption } from 'ky';
const parentController = new AbortController();
const childController = new AbortController();
const base = ky.create({ signal: parentController.signal });
const independent = base.extend({
signal: replaceOption(childController.signal),
});
在这一正式实现下,independent 的调用方取消信号来自 childController。base 中的认证、超时、请求钩子等其他选项仍按各自合并规则继承。replaceOption 只包住 signal,不是在重建整个客户端。
如果派生客户端本来应该跟随页面退出而取消,就不适合用这段配置把父级生命周期移除。独立取消也不会移除 Ky 自己的请求超时;不要把超时错误误判成旧父信号仍被继承。
旧版本不要靠 null 猜测清除行为
旧实现中,传入 undefined 或 null 也可能没有清掉已经收集的父信号。因此,在该路径存在问题的版本里反复换这几个值,并不能证明取消关系已经解除。
优先在实际安装包中核对解包和信号收集顺序,再采用包含修正的正式版本。当前可确认 2.2.0 有上述处理,不据此声称所有较早版本都受影响,也不把某个分支补丁当成已发布版本。
暂不能升级、又确实需要完全不同生命周期时,可以评估用 ky.create() 从完整业务配置创建新客户端。它不会继承旧实例:认证、超时、错误处理和其他钩子必须逐项写回,不能只复制 signal 后直接替换现有封装。
已经 aborted 的信号不能恢复成未取消状态。复用同一个 childController 发后续请求,会把它们立即取消;每个新的生命周期需要新的控制器,不应删除异常或吞掉它来伪装成功。
在受控的只读慢响应上分别验证父取消、子取消、正常完成和自然超时。显式替换场景中,父取消不应终止派生请求,子取消应终止它;同时核对底层网络记录,不能仅凭业务 Promise 的状态判断请求已停止。


