在RXJS中,如何使用retryWhen强制重新执行使用bindNodeCallback创建的可观察对象?

在RXJS中,如何使用retryWhen强制重新执行使用bindNodeCallback创建的可观察对象?,rxjs,rxjs6,Rxjs,Rxjs6,我有一个Subject,它接收对象,然后调用我用bindNodeCallback包装的函数。如果由于错误或结果为false而失败,我想使用retryWhen重试该函数的执行 我尝试了几种不同的方法,但没有成功地使绑定函数再次启动 我有一本书 函数fakeSend( 任务:字符串, cb:(err:Error | null,结果?:boolean)=>void ) { console.log(“fakesend”,任务); 设置超时(()=>{ const hasrerror=Math.rando

我有一个
Subject
,它接收对象,然后调用我用
bindNodeCallback
包装的函数。如果由于错误或结果为false而失败,我想使用
retryWhen
重试该函数的执行

我尝试了几种不同的方法,但没有成功地使绑定函数再次启动

我有一本书

函数fakeSend(
任务:字符串,
cb:(err:Error | null,结果?:boolean)=>void
) {
console.log(“fakesend”,任务);
设置超时(()=>{
const hasrerror=Math.random()<0.5;
const res=Math.random()<0.5;
log(hasError?“hasError”:`用${res}响应');
if(hasrerror){
返回cb(新错误(“错误”));
}
返回cb(null,res);
}, 100);
}
const boundSend=bindNodeCallback(fakeSend);
常量主题=新主题();
subject.subscribe(
(任务)=>{
边界发送(任务)
.烟斗(
轻触((状态)=>{
如果(!状态){
抛出新错误(“未发送”);
}
返回状态;
}),
retryWhen((错误)=>
烟斗(
延迟(1000),
轻触((错误)=>console.log)
)
)
)
.订阅({
下一步:console.log,
错误:console.error,
完成:()=>{
console.log(“完成”,任务);
}
});
},
(错误)=>{
console.log(“主题订阅错误”);
}
);
主题。下一步(“测试1”);
主题。下一步(“测试2”);
主题。下一步(“测试3”);
主题。下一步(“测试4”);
谢谢

const{Subject,bindNodeCallback}=rxjs;
const{tap,retryWhen,delay}=rxjs.operators;
功能伪造(
任务
cb
) {
console.log(“fakesend”,任务);
设置超时(()=>{
const hasrerror=Math.random()<0.5;
const res=Math.random()<0.5;
log(hasError?“hasError”:`用${res}响应');
if(hasrerror){
返回cb(新错误(“错误”));
}
返回cb(null,res);
}, 100);
}
const boundSend=bindNodeCallback(fakeSend);
常量主题=新主题();
subject.subscribe(
(任务)=>{
边界发送(任务)
.烟斗(
轻触((状态)=>{
console.log('tap');
如果(!状态){
抛出新错误(“未发送”);
}
返回状态;
}),
retryWhen((错误)=>
烟斗(
延迟(1000),
轻触((错误)=>console.log)
)
)
)
.订阅({
下一步:console.log,
错误:console.error,
完成:()=>{
console.log(“完成”,任务);
}
});
},
(错误)=>{
console.log(“主题订阅错误”);
}
);
主题。下一步(“测试1”);
主题。下一步(“测试2”);
主题。下一步(“测试3”);
主题。下一步(“测试4”)

如果希望在出现错误时通过
retryWhen
操作符重试基于回调的函数,我认为您可以尝试使用
新的Observable
构造函数,而不是
bindNodeCallback
。下面是一些细节

首先,您可以使用如下的
新的Observable
构造函数从基于回调的函数中创建一个Observable

function newSend(task: string) {
  return new Observable(
    (subscriber: Subscriber<string>): TeardownLogic => {
      fakeSend(task, (err: Error | null, result?: string) => {
        if (err) {
          subscriber.error(err);
        } else {
          subscriber.next(result);
          subscriber.complete();
        }
      });
    }
  );
}
subject
  .pipe(
    mergeMap((task) => {
      return newSend(task);
    })
  )
  .subscribe(
    (data) => console.log("Notification", data),
    (error) => {
      console.log("Error in final subscription", error);
    },
    () => console.log("DONE")
  );

subject.next("test1");
subject.next("test2");
subject.next("test3");
subject.next("test4");

subject.complete();  // added just to show that the complete function of the subscriber can be invoked
subject
  .pipe(
    mergeMap((task) => {
      return newSend(task).pipe(
        retryWhen((errs) =>
          errs.pipe(
            delay(1000),
            tap((err) => console.log("Retry", err.message))
          )
        )
      );
    })
  )
如果我们执行上述代码,我们会看到主题上的订阅将所有成功调用记录到
fakeSend
,直到遇到错误,此时它会记录错误并终止(请参阅)

然后,我们可以添加
retryWhen
操作符,以便在
fakeSend
回调出现错误时实际重试,如下所示

function newSend(task: string) {
  return new Observable(
    (subscriber: Subscriber<string>): TeardownLogic => {
      fakeSend(task, (err: Error | null, result?: string) => {
        if (err) {
          subscriber.error(err);
        } else {
          subscriber.next(result);
          subscriber.complete();
        }
      });
    }
  );
}
subject
  .pipe(
    mergeMap((task) => {
      return newSend(task);
    })
  )
  .subscribe(
    (data) => console.log("Notification", data),
    (error) => {
      console.log("Error in final subscription", error);
    },
    () => console.log("DONE")
  );

subject.next("test1");
subject.next("test2");
subject.next("test3");
subject.next("test4");

subject.complete();  // added just to show that the complete function of the subscriber can be invoked
subject
  .pipe(
    mergeMap((task) => {
      return newSend(task).pipe(
        retryWhen((errs) =>
          errs.pipe(
            delay(1000),
            tap((err) => console.log("Retry", err.message))
          )
        )
      );
    })
  )
在这一点上,我们的逻辑是完整的。每当调用带有错误的
fakeSend
回调时,
retryWhen
操作符将确保使用相同参数执行对
fakeSend
的新调用。完整的代码可以在中看到

为什么它不能与
bindNodeCallback一起使用


原因是,
bindNodeCallback
的实现在内部使用了一个
AsyncSubject
,它缓存最后一个结果,并在后续订阅中重放它。因此,如果基于回调的函数出错,并且
retryWhen
操作符再次订阅,那么订阅将再次生成相同的错误,从而启动无限循环。如果您将
newSend(task)
替换为
boundSend(task)
(第46行和第47行)

可以看出这一点。我使您的脚本可以在本地代码段中运行。如果有解决方案,则可以更轻松地复制代码段以回答问题you@AdrianBrand太棒了,谢谢你!最好将问题的关联代码存储在StackOverflow上,而不是外部站点上。希望我们能很快得到TypeScript代码段的支持。提示StackOverflow开发者!也许即使有npm支持,我们也不必依赖CDN。你不应该在rxjs中看到嵌套订阅。我吃了一顿大醉的午餐,现在还没来得及用rxjs思考,但如果没有其他人插话的话,我可能明天再看。谢谢你如此透彻的解释。你真是太棒了。这是一个有趣的问题,也帮助了我