Ky extend 后旧方法仍会重试:retry 数组被合并了
Ky extend会深合并重试数组,子实例写入新methods不代表旧方法已被排除。使用正式replaceOption替换完整retry对象,重新确定次数、状态码及其他需要保留的策略。
父 Ky 实例只允许 GET 重试,派生实例把 retry.methods 写成另一组方法,旧方法却仍然会重试。调用没有报错,配置也看似被覆盖,问题出在对 .extend() 合并方式的理解。
Ky 的 extend 会深合并选项。正式 2.2.0 中,普通数组仍会拼接;父级 ['get'] 加上子级 ['head'],结果包含两者,并不是只剩 HEAD。retry.statusCodes、retry.afterStatusCodes 也需要按同样的合并层级检查。
指定数组不等于清掉父级数组
下面只是实例配置,未发出请求:
import ky from 'ky';
const base = ky.create({
retry: {
limit: 2,
methods: ['get'],
statusCodes: [500],
},
});
const derived = base.extend({
retry: {
methods: ['head'],
},
});
这里 derived 仍保留父级 GET。传入空数组也不能用来可靠地清空继承集合。若父级包含 POST,子级只写 GET 并不代表写请求已经被排除;是否真的重试,还受错误类型、状态码、次数和钩子等条件影响。
这不是要求把所有深合并逻辑改成浅合并。已有认证头、钩子与其他配置可能依赖继承规则,直接修改公共客户端工厂会改变其他调用的行为。
明确替换整个 retry,并重新写全所需策略
正式 2.2.0 导出 replaceOption()。如果意图是让派生实例采用一份完整的新重试策略,可以只包住 retry:
import ky, { replaceOption } from 'ky';
const base = ky.create({
retry: {
limit: 2,
methods: ['get'],
statusCodes: [500],
},
});
const headOnly = base.extend({
retry: replaceOption({
limit: 2,
methods: ['head'],
statusCodes: [500],
afterStatusCodes: [],
}),
});
这里替换的是整个 retry 对象,父级原来配置的其他重试选项不会因继承自动保留下来。项目原有的延迟、最大等待、超时重试或自定义判断需要逐项决定是否写回;示例只表达“HEAD、500、最多两次额外尝试,并且不按状态列表读取重试时间头”的策略。
替换 retry 不会同时清除实例其他顶层选项,例如认证头和请求钩子。若某个钩子主动要求重试,或业务外层另有重试循环,仍须一起核对,不能只凭 methods 数组判断所有发送行为。
更细粒度的替换应对照实际安装版本支持的层级使用,不要把较早版本只有整对象替换的行为与当前实现混在一起。没有 replaceOption 的旧依赖,可评估创建一份完整配置的新实例;但 ky.create() 不继承父实例,认证、超时、钩子和业务封装都要明确保留,不能只复制 retry 后就替换生产客户端。
检查被禁止的方法是否真的只发送一次
在无副作用的受控端点,让允许的方法遇到指定状态码,核对实际尝试次数;再让被排除的方法遇到相同条件,确认没有因父级策略继续重试。示例中的 limit 为 2,指初次请求之外最多两次重试,而非总计只请求两次。
写请求尤其要先确认幂等键、请求体能否重放和失败后的结果查询。已经发生过的重复提交,应按业务记录确认,不能靠改好派生实例配置就假定旧请求没有执行。


