使用 rxjs 实现指数退避

2023-11-26

角7docs提供这个实际用法的例子rxjs Observable为 AJAX 请求实现指数退避:

import { pipe, range, timer, zip } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { retryWhen, map, mergeMap } from 'rxjs/operators';

function backoff(maxTries, ms) {
 return pipe(
   retryWhen(attempts => range(1, maxTries)
     .pipe(
       zip(attempts, (i) => i),
       map(i => i * i),
       mergeMap(i =>  timer(i * ms))
     )
   )
 );
}

ajax('/api/endpoint')
  .pipe(backoff(3, 250))
  .subscribe(data => handleData(data));

function handleData(data) {
  // ...
}

虽然我理解 Observables 和 back off 的概念,但我不太清楚,到底如何retryWhen将计算重新订阅源的时间间隔ajax.

具体来说,如何zip, map, and mapMerge在此设置中工作?

以及其中将包含什么attempts对象被发射到retryWhen?

我浏览了他们的参考页面,但仍然无法理解这一点。


我花了相当多的时间研究这个(出于学习目的),并将尝试尽可能彻底地解释这段代码的工作原理。

首先,这是原始代码,带注释:

import { pipe, range, timer, zip } from 'rxjs';
import { ajax } from 'rxjs/ajax';
import { retryWhen, map, mergeMap } from 'rxjs/operators';

function backoff(maxTries, ms) {                  // (1)
 return pipe(                                     // (2)
   retryWhen(attempts => range(1, maxTries)       // (3)
     .pipe(
       zip(attempts, (i) => i),                   // (4)
       map(i => i * i),                           // (5)
       mergeMap(i =>  timer(i * ms))              // (6)
     )
   )
 );                                               // (7)
}

ajax('/api/endpoint')
  .pipe(backoff(3, 250))
  .subscribe(data => handleData(data));

function handleData(data) {
  // ...
}
  1. 很简单,我们正在创建自定义backoff运算符出retryWhen操作员。我们稍后可以应用它pipe功能。
  2. 在此背景下,pipe方法返回一个自定义运算符。
  3. 我们的自定义操作符将被修改retryWhen操作员。它需要一个函数参数。这个函数将被调用once-具体来说,当这个retryWhen第一次遇到/调用。顺便一提,retryWhen开始发挥作用only当源可观察量产生错误时。然后,它会防止错误进一步传播并重新订阅源。如果源产生非错误结果(无论是在第一次订阅还是在重试时),retryWhen被忽略并且不涉及。

    几句话attempts。这是一个可观察的。它不是可观察到的源。它是专门为retryWhen。它只有一种用途:每当订阅(或重新订阅)源可观察结果导致错误时,attempts发射一个next。我们被给予attempts并可以自由地使用它,以便以某种方式对源可观察的每个失败的订阅尝试做出反应。

    这就是我们要做的。

    首先我们创建range(1, maxTries),一个可观察量,对于我们愿意执行的每次重试都有一个整数。range已准备好立即发射所有号码,但我们必须按兵不动:只有在另一次重试发生时,我们才需要一个新号码。所以,这就是为什么我们...

  4. ... 用拉链将其压缩attempts。意思是,将每个发出的值结合起来attempts具有单一值range.

    请记住,我们当前所在的函数只会被调用一次,那时,attempts只会被解雇next一次——对于最初失败的订阅。因此,此时,我们的两个压缩可观察量仅产生一个值。

    顺便说一句,压缩成一个的两个可观察量的值是多少?该函数决定:(i) => i。为了清楚起见,可以写成(itemFromRange, itemFromAttempts) => itemFromRange。第二个参数没有使用,所以它被删除,第一个被重命名为i.

    这里发生的事情是我们简单地忽略了values被解雇attempts,我们只对fact他们被解雇了。每当这种情况发生时,我们都会从中提取下一个值range可观察到的...

  5. ...并将其平方。这是为了指数指数退避的一部分。

    因此,现在每当(重新)订阅源失败时,我们手上就会有一个不断增加的整数(1、4、9、16...)。我们如何将该整数转换为下次重新订阅之前的时间延迟?

    请记住,我们当前所在的这个函数,它必须返回一个可观察的,使用attempts作为输入。由此产生的可观察对象仅构建一次。retryWhen然后订阅生成的 observable,并且:每当生成的 observable 触发时,重试订阅源 observablenext; calls complete or error每当结果 observable 触发那些相应的事件时,就在源 observable 上。

  6. 长话短说,我们需要做retryWhen稍等一会。delay也许可以使用运算符,但是设置延迟的指数增长可能会很痛苦。反而,mergeMap运算符开始发挥作用。

    mergeMap是两个运算符组合的快捷方式:map and mergeAll. map只需将每个递增整数 (1, 4, 9, 16...) 转换为timer可观察到的触发next经过毫秒数后。mergeAll forces retryWhen实际订阅timer。如果最后一点没有发生,我们得到的 observable 就会被触发next立即与timer可观察实例作为值。

  7. 此时,我们已经构建了自定义可观察对象,它将被使用retryWhen决定何时尝试重新订阅源可观察对象。

就目前情况而言,我发现此实现有两个问题:

  • 一旦我们产生的可观察到的火焰最后一次next(导致最后一次尝试重新订阅),它也会立即触发complete。除非源 observable 返回结果very很快(假设最后一次重试将成功),该结果将被忽略。

    这是因为一旦retryWhen hears complete从我们的观察来看,它调用complete在源上,它可能仍在发出 AJAX 请求的过程中。

  • 如果所有重试均不成功,则源实际上会调用complete而不是更符合逻辑error.

为了解决这两个问题,我认为我们生成的可观察值应该触发error最后,在给最后一次重试一些合理的时间来尝试完成其工作之后。

这是我对上述修复的实现,其中还考虑了弃用zip最新的运算符rxjs v6:

import { delay, dematerialize, map, materialize, retryWhen, switchMap } from "rxjs/operators";
import { concat, pipe, range, throwError, timer, zip } from "rxjs";

function backoffImproved(maxTries, ms) {
    return pipe(
        retryWhen(attempts => {
            const observableForRetries =
                zip(range(1, maxTries), attempts)
                    .pipe(
                        map(([elemFromRange, elemFromAttempts]) => elemFromRange),
                        map(i => i * i),
                        switchMap(i => timer(i * ms))
                    );
            const observableForFailure =
                throwError(new Error('Could not complete AJAX request'))
                    .pipe(
                        materialize(),
                        delay(1000),
                        dematerialize()
                    );
            return concat(observableForRetries, observableForFailure);
        })
    );
}

我测试了这段代码,它似乎在所有情况下都能正常工作。我现在懒得详细解释;我怀疑有人会读上面的文字墙。

不管怎样,非常感谢@BenjaminGruenbaum 和@cartant 让我走上正确的道路,让我的头脑明白这一切。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 rxjs 实现指数退避 的相关文章

  • React延迟加载/无限滚动解决方案

    我花了一段时间才弄清楚如何使用优秀的延迟加载图像React Lazyload 组件 https github com jasonslyvia react lazyload 演示在滚动时延迟加载图像 但在测试时我无法获得相同的行为 罪魁祸首是
  • 区分单击与 mousedown/mouseup

    我已经阅读了有关这种情况的 stackoverflow 上的几个答案 但没有一个解决方案有效 我尝试根据用户是否单击某个元素或使用 jQuery 将鼠标按住该元素来执行不同的操作 有可能做到这一点吗 onMouseDown 将在按下左侧或右
  • 代码点火器 JSON

    你好 我使用 codeigniter 然后我从控制器中的数据库中回显输出 然后在我的视图文件中执行以下操作 但它没有显示任何内容 S 我的模型文件 function forumList this gt db gt select oversk
  • JSDoc:如何在生成的文档中包含自定义 css 文件模板?

    JS文档docs https jsdoc app about configuring default template html say 将图像目录复制到输出目录 复制全部 将 myproject static 中的静态文件复制到输出目录
  • WebDriver:更改事件未触发

    我有一个使用 KnockoutJS 的应用程序 我正在尝试编写一些测试表单的测试 如果您不了解 KnockoutJS 简单来说 它提供了从我的视图到我的数据模型的绑定 这意味着当我在输入字段中键入值时 我的基础对象会自动使用该输入字段值进行
  • 如何通过角度2中的父组件将自定义(html)模板传递给子组件?

  • 正则表达式 - 避免表达式中出现字符串

    我正在尝试创建一个应该匹配以下情况的正则表达式 如果单词完全匹配 first second third 那么匹配应该失败 但如果它周围有任何字符 那么应该匹配该字符串 我还需要避免字符串中的某些字符集 如果这些字符是字符串的一部分 则匹配结
  • django ajax post 403被禁止

    使用 django 1 4 当我尝试从我的 javascript 做我的 django 服务器上的帖子时 我收到 403 错误 我的 get 工作正常 尽管问题仅出在帖子上 也尝试过 csrf exempt但没有运气 更新 我现在可以发布我
  • 禁用任何类型的浏览器窗口滚动?

    有没有办法禁用滚动 不仅仅是滚动条 还有浏览器窗口的全部功能 根据您对 Keit 的回答 您不想在打开灯箱时滚动处于活动状态 如果是这种情况 您可以使用以下 css 在打开灯箱的同时向正文添加一个类 这个解决方案的好处是它保留了滚动 空间
  • Web 应用程序中的 PathLocationStrategy 与 HashLocationStrategy

    使用的优点和缺点是什么 PathLocationStrategy 默认的 HTML 5 PushState 样式 HashLocationStrategy 哈希 URL 样式 例如 使用哈希位置策略将阻止通过 ID 滚动到元素的功能 但某些
  • 如何在 OpenLayers 3 中删除监听器

    我做了一个copy https gis stackexchange com questions 178222 how to delete a listener in openlayers 3我在 stackoverflow 上提出的问题 因
  • Google 地图 v3 中标准缩放控件的样式

    有没有一种简单的方法可以在 Google Maps JavaScript API v3 中设置缩放控件的样式 我想要的只是改变标准图像 http maps gstatic com intl en ALL mapfiles mapcontro
  • IE6 丢失查询字符串

    我有一个使用 javascript 从查询字符串中获取值的页面window location 从网络服务器运行时效果很好 但如果我通过将其放在地址栏中使用 IE6 在本地运行它 c mysite index htm 网站创建的任何查询字符串
  • Jade(当前称为“Pug”)模板引擎中的循环

    我想使用一个简单的循环 例如for int i 0 i lt 10 i 如何在 Jade 引擎中使用它 我正在使用 Node js 并使用expressjs 框架 例如 for var i 0 i lt 10 i li array i 你可
  • 盒式捆绑包与 MVC4 捆绑包

    我目前正在开发一个原型 ASP NET MVC 3 解决方案 该解决方案将用作多个项目重写的基础 来自 Web 表单 我的目标之一是跨应用程序实现一些脚本管理 而不是我们目前没有的目标 MVC 3有一个缺陷恕我直言 如果您需要在部分视图或模
  • 使用 JavaScript 从 URL 变量读取来加载不同的 CSS 样式表

    我试图在我的 WordPress 博客上使用两个不同的样式表 以便在通过 Web 访问页面时使用一个样式表 而在通过我们的 iOS 应用程序访问博客内容时使用另一个样式表 现在 我们将 app true 附加到来自 iOS 应用程序的 UR
  • 如何在 ChartJS 中创建自定义图例

    我需要使用 ChartJS 库为我的圆环图创建自定义图例 我已经使用 ChartJS 提供的默认图例创建了甜甜圈 但我需要一些修改 我希望其价值高于汽车名称 另外 我不喜欢粘性图例 我想将其与甜甜圈分开 这样我就可以更改字体 框的样式 例如
  • Chrome Prerender 功能每次都会被取消

    我正在尝试 Chrome 中的预渲染功能 但是当我检查网络时 我可以看到任何链接的请求都被取消 我使用以下语法 我尝试了现场演示http prerender test appspot com http prerender test apps
  • jQuery fadeOut 一个 div,fadeIn 另一个 div 在其位置

    我正在尝试一个简单的 jQuery 脚本来淡出一个 div 并淡入另一个 div 但由于某种原因 第一个 div 永远不会淡出 这可能是代码的一个明显问题 但我似乎无法弄清楚 div div div div
  • 使用 stopPropagation() 处理 React 事件委托

    我有一个 React 项目 应该可以放置在任何网站上 我的想法是 我托管一个 javascript 文件 人们放置一个具有特定 ID 的 div 然后 React 在该 div 中进行渲染 到目前为止 除了点击事件之外 这是有效的 这些事件

随机推荐

  • Java中如何从HTTP请求中获取JSON对象

    我现在尝试使用 Java cord 中的 HTTP 请求获取 JSON 对象 我想知道如何在下面的代码中获取响应或 JSON 对象 请告诉我 在这个程序中 我尝试获取文章 纽约 的维基百科类别 String requestURL http
  • Laravel 4 自定义验证规则 - 在哪里扩展验证器?

    我想制定一个自定义验证规则 我的模型现在看起来像这样 protected rules array first name gt required last name gt required ssn gt required integer mi
  • 如何向 ubuntu 应用程序添加图标

    我已经用Python快速开发了一个用于ubuntu的应用程序 我将其打包并且工作正常 但我找不到如何向我的应用程序添加图标 目前它就像一个空白图标 我用过 Glade 和 gtk 最好的方法是安装一个 命名图标 为此 您的安装脚本会将文件复
  • Python,可变长度位置参数之后的默认关键字参数

    我以为我可以在 Python 2 中的函数调用中的可变长度位置参数之后使用命名参数 但我得到了SyntaxError导入 python 类时 我正在使用以下 get 方法编写 例如 class Foo object def init sel
  • 停止自托管owin服务器时完成当前请求

    我有 OWIN 服务器作为控制台应用程序的一部分 你可以在这里看到主要方法 class Program public static ManualResetEventSlim StopSwitch new ManualResetEventSl
  • 从 UIBezierPath 获取点

    我通过以下方式绘制了上面的 BezierPath 位置是用户触摸屏幕的位置 位置将是图表的最大值 CGPoint 原点 CGPointMake xStart 620 0 CGPoint endpt CGPointMake xEnd 620
  • 使用 Hibernate 公式从另一个表中选择实体

    我正在尝试使用 Hibernate 的 Formula 注释从另一个表中检索实体 给出以下代码 Entity class Test Id GeneratedValue strategy GenerationType IDENTITY Col
  • 无法使用 pymongo 2.2 连接到 MongoDB 2.0.5 数据库

    我已经诊断这个问题几个小时了 我想看看是否有 pymongo 专家有任何想法 下面一行代码 连接 pymongo Connection localhost 27017 生成以下错误 usr local Cellar python 2 7 3
  • .htaccess 虚拟主机的基本身份验证?

    我想知道是否可以根据 htaccess 文件中的虚拟主机 URL 设置条件 http 基本身份验证要求 例如 我想要做的是让 mysite com 和 test mysite com 在同一目录中运行相同的代码库 但密码保护 test my
  • SceneKit 每个顶点颜色

    我一直在使用 SceneKit 但我不知道如何创建每个顶点颜色几何体 更准确地说 我想这样做 http openglbook com chapter 2 vertices and shapes html 如果不清楚 请告诉我 Thanks
  • 如何让触摸事件影响容器视图后面的视图?

    我有一个完全覆盖另一个 UIView 的容器视图 容器视图具有透明度以及其他一些功能 搜索栏 表格视图等 我希望触摸事件能够通过容器视图 并在事件发生在透明区域时影响下面的视图 我一直在摆弄容器视图的子类 我试图让 pointInside
  • 如何在Windows 10通用应用程序中使用WCF服务?

    我的 Windows 8 1 应用程序使用 WCF 服务 我需要将我的应用程序移植到 Windows 10 UWP 应用程序 但无法添加服务引用 当我添加服务引用时出现此消息 数据服务客户端代码生成失败 不支持指定的 Windows 应用商
  • 如何从字符串数组或数组列表创建字符串?

    我如何提取字符串 或数组列表中的所有元素 并将所有单词以正确的格式 带有单个空格 组合在一起 然后存储在数组中 String a Java is cool 输出 Java 很酷 Use a StringBuilder String stri
  • 如何加快 Eclipse 项目“刷新”速度

    我有一个相当大的 PHP 代码库 10k 文件 我在 Windows 计算机上使用 Eclipse 3 4 PDT 2 来处理它 而这些文件托管在 Debian 文件服务器上 我通过 Windows 上的映射驱动器进行连接 尽管有 1gbi
  • Ruby 相当于 PHP 的 $this

    相当于PHP的什么 this gt 在鲁比 红宝石相当于this is self 它们都指当前实例 棘手的部分是在 Ruby 类范围内 self指的是该类的当前实例Class它定义了您正在构建的类 在方法内部 self指类的实例 eg cl
  • 在 x86-64 中将寄存器移至自身有什么好处

    我正在 x86 64 NASM 中做一个项目 并遇到了指令 mov rdi rdi 在我的教授写的编译器的输出中 我已经进行了全面搜索 但找不到提及为什么需要这样做 它会影响标志还是我不明白的聪明之处 为了提供一些上下文 它在同一寄存器递减
  • Google Analytics API 错误“无法同时查询选定的维度和指标。”

    我必须同时从 GA 电子商务中检索尽可能多的不同指标 我正在使用 google api ruby 客户端 并且不断出现错误 message gt Selected dimensions and metrics cannot be queri
  • 存档上传失败并出现错误:ITMS-90470 缺少 TVTopShelfImage.TVTopShelfPrimaryImageWide 密钥

    您知道为什么会发生这种情况以及最重要的是如何解决它吗 添加具有什么值的密钥 从 tvOS 10 开始 您必须包含宽顶架图像 Top Shelf Image Wide 大小为2320px by 720px 1x tvOS 人机界面指南 图标和
  • 无法加载 share\octave\packages 中可用但未在 share\octave\octave_packages 中列出的包

    短篇故事 在目录中 octave 4 2 1 share octave packages最初有 45 个包子目录 其中 m和其他文件 例如financial 0 5 0 但我找不到使用它们的方法 更详细 我已经解压了便携式 zip 版本Oc
  • 使用 rxjs 实现指数退避

    角7docs提供这个实际用法的例子rxjs Observable为 AJAX 请求实现指数退避 import pipe range timer zip from rxjs import ajax from rxjs ajax import