资讯详情

资讯详情

RxJS v4 retry 操作符深度解析:重试语义、源码原理与实战用法

RxJS v4 retry 操作符深度解析重试语义、源码原理与实战用法【免费下载链接】RxJSThe Reactive Extensions for JavaScript项目地址: https://gitcode.com/gh_mirrors/rxj/RxJS本指南围绕 RxJS v4 中Rx.Observable.prototype.retry([retryCount])操作符展开系统讲解其失败后自动重订阅的核心语义、retryCount参数的精确含义含常见的 off-by-one 陷阱、完整可运行的示例并结合仓库源码与单元测试揭示其底层实现原理。读完本文你将掌握 retry 的正确用法、参数取值规则以及它与retryWhen的选型边界。retry 是什么失败重试的核心语义retry是 RxJS 中用于错误恢复error recovery的基础操作符。它的行为可以概括为重复订阅源 Observable 指定的次数直到它成功终止发出 onCompleted为止。如果不指定次数则无限重试。其 API 签名如下API 文档Rx.Observable.prototype.retry([retryCount])它只关心一个条件源序列是否以onError结束。源序列正常完成onCompleted→ retry 不做任何额外动作直接透传完成信号源序列抛出错误onError→ retry 重新订阅源序列再次执行重试耗尽指定了retryCount且达到上限后仍出错 → 将最后一次的错误转发给下游未指定retryCount→ 无限重试序列可能永远不会终止。参数说明参数类型说明retryCount可选Number重试重新订阅序列的次数。如果不提供则无限重试。返回值返回一个Observable它反复产生源序列的元素直到源序列成功终止。最容易踩的坑retryCount 的 off-by-one 语义文档中特别强调了一句话Note if you encounter an error and want it to retry once, then you must use .retry(2).这句话值得仔细解读。retryCount的语义并不是第一次失败之后再重试 N 次而是总共尝试订阅 N 次即总订阅次数 1首次订阅 重试次数也就是说retry(n)最多允许源序列失败n - 1次。想要失败后重试 1 次必须写retry(2)想要失败后重试 2 次必须写retry(3)。这一点与许多人对重试次数的直觉不同是使用 retry 时最常见的错误来源。这一语义在源码与测试中都有明确印证详见下文源码剖析与测试解读部分。完整示例带重试的取值流程文档给出的示例组合了interval、selectMany、throw、return和take演示了一个前两次失败、第三次成功的典型重试场景var count 0; var source Rx.Observable.interval(1000) .selectMany(function () { if (count 2) { return Rx.Observable.throw(new Error()); } return Rx.Observable.return(42); }) .retry(3) .take(1); var subscription source.subscribe( function (x) { console.log(Next: x); }, function (err) { console.log(Error: err); }, function () { console.log(Completed); }); // Next: 42 // Completed运行流程拆解interval(1000)每隔 1 秒发射一个递增整数selectMany在每次发射时根据计数器决定返回抛出错误的序列还是返回 42 的序列第 1、2 次count为 1、2都抛出错误第 3 次count为 3返回 42retry(3)允许总共订阅 3 次第 1 次、第 2 次以错误结束触发重新订阅第 3 次成功发出 42take(1)拿到第一个元素后立刻完成。因此最终控制台输出为Next: 42和Completed——注意这里没有Error输出因为错误在重试过程中被内部消化了只有重试次数耗尽时错误才会传播给下游观察者。源码级剖析retry 背后的组合实现经典实现src/core在核心实现 src/core/linq/observable/retry.js 中整个retry只由一行组合代码构成observableProto.retry function (retryCount) { return enumerableRepeat(this, retryCount).catchError(); };它把 retry 拆解为两个既有原语的组合enumerableRepeat(this, retryCount)把源 Observable 包装成一个可重复取值的枚举序列。核心逻辑在 src/core/enumerable.js 的RepeatEnumerable中retryCount为null时被规范化为-1表示无限重复迭代器每次next()返回同一个源序列并在remaining 0时返回done: true结束迭代。.catchError()依次订阅这个枚举序列中的每个源若某个源以错误结束就记住lastError并继续取下一个源重新订阅只有枚举耗尽done: true时才把最后一次的lastError通过onError抛给下游否则走onCompleted。对应的CatchErrorObservable与InnerObserver同样定义在 src/core/enumerable.js。模块化实现src/modular在按模块拆分的新版实现 src/modular/observable/retry.js 中同一逻辑被重写为独立的CatchErrorObservable但语义完全一致可以相互印证repeat(value, count)工厂函数第 15-29 行同样把count null归一化为-1无限迭代器在remaining 0时终止CatchErrorObserver第 31-41 行在error回调中记录state.lastError并调用recurse(state)触发下一次订阅在completed回调中直接透传onCompletedscheduleMethod第 62-73 行取出下一个源若迭代结束则依据lastError是否为null决定是onError(lastError)还是onCompleted()否则用SingleAssignmentDisposable订阅当前源支持 Promise 输入isPromise(currentValue) (currentValue fromPromise(currentValue))即源序列中的值如果是 Promise 会被自动转换为 Observable整体调度使用Scheduler.queue.scheduleRecursive并返回一个NAryDisposable聚合了订阅、调度与状态三者的释放器保证中途取消订阅时不会发生资源泄漏。从这段源码可以确认几个关键行为成功即停止只要某一次订阅以onCompleted结束CatchErrorObserver.completed直接向下游发送完成枚举立即终止不会继续消耗剩余的重试次数重试是重订阅而非重放retry 重新执行的是源序列的订阅过程。如果源是冷的coldObservable如网络请求、interval每次重试都会重新产生副作用如果源是热的hotObservable重订阅可能拿不到之前已经过去的事件错误只在耗尽时上抛重试期间的错误对下游观察者是不可见的只有最后一次失败才会被传播。单元测试验证从测试用例看行为边界仓库中的 tests/observable/retry.js 用TestScheduler精确验证了 retry 的时序与订阅次数是对上述语义最直接的证据retry Observable basic第 17-41 行源序列正常完成retry()只产生一次订阅subscribe(200, 450)元素按原时序透传并最终onCompleted——证明成功时不做多余重试retry Observable error第 67-102 行不传次数时源序列反复以错误结束测试在disposed: 1100时手动取消。订阅记录显示序列被连续重新订阅了 4 次subscribe(200, 450)、subscribe(450, 700)、subscribe(700, 950)、subscribe(950, 1100)元素持续重放——证明无参数 无限重试retry Observable retry count basic第 140-174 行retry(3)时订阅记录恰好为 3 次subscribe(200, 220)、subscribe(220, 240)、subscribe(240, 260)第 3 次仍以错误结束时最终在 260ms 处收到onError(error)——这是retryCount表示总订阅次数、错误只在耗尽后上抛的最有力证据retry Observable retry count dispose第 176-204 行在第 2 次重试过程中231ms 处取消订阅后续元素不再发射订阅也被正确终止——证明中途取消订阅是安全且及时的retry Observable retry count Throws第 256-296 行覆盖了观察者回调内部抛异常、源序列同步抛异常等边界情况说明 retry 对异常的回传路径与普通订阅一致。与 retryWhen 的选型何时需要更高级的重试策略当需要每次重试之间延迟一段时间、按指数退避backoff、达到失败次数后改为完成或抛出指定错误等精细化控制时retry的固定次数模型就不够用了。此时应使用同族操作符retryWhen(notificationHandler)API 文档实现见 src/modular/observable/retrywhen.js。retryWhen的核心不同点在于它把源序列产生的错误流一个Subject交给用户提供的处理器notificationHandler由处理器决定重试节奏处理器发出 next→ 触发一次重订阅处理器完成onCompleted→ 源序列直接以完成结束处理器出错onError→ 该错误被转发给下游终止整个序列。例如固定延迟重试Rx.Observable.interval(1000) .map(function (n) { if (n 2) { throw ex; } return n; }) .retryWhen(function (errors) { return errors.delay(200); // 每次出错后延迟 200ms 再重试 }) .take(6);再如指数退避依次延迟 1、2、3 秒Rx.Observable.create(function (o) { console.log(subscribing); o.onError(new Error(always fails)); }).retryWhen(function (attempts) { return Rx.Observable.range(1, 3).zip(attempts, function (i) { return i; }) .flatMap(function (i) { console.log(delay retry by i second(s)); return Rx.Observable.timer(i * 1000); }); }).subscribe();选型建议固定次数、无间隔、失败即重来的场景用retry最简洁需要延迟、退避、限次后改变结束方式的场景用retryWhen。使用注意事项热/冷序列差异retry 通过重新订阅实现重试对冷序列如 HTTP 请求、文件读取每次重试都会重新发起对热序列如fromEvent包装的事件流重订阅通常不会重新产生历史事件实际效果可能不符合预期无限重试的风险不传retryCount时若源持续失败序列永不终止注意与take、timeout等操作符组合使用或在订阅端做好释放subscription.dispose()副作用重复执行源序列订阅过程中的副作用日志、计数器、请求会在每次重试时重复执行示例中的count正是利用了这一特性对 Promise 的兼容模块化实现中源元素若是 Promise 会被自动转换为 Observable 后再订阅重试语义同样适用。相关资源位置API 文档doc/api/core/operators/retry.md核心实现src/core/linq/observable/retry.js、src/core/enumerable.js模块化实现src/modular/observable/retry.js单元测试tests/observable/retry.js发布物retry属于核心基础操作符随rx.all.js、rx.js、rx.lite.js等发行包一并发布对应 NPM 包rxNuGet 包RxJS-All、RxJS-Main、RxJS-Lite无额外前置依赖Prerequisites: None。【免费下载链接】RxJSThe Reactive Extensions for JavaScript项目地址: https://gitcode.com/gh_mirrors/rxj/RxJS创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →