[英]RxJS retry not behaving as expected?
我在让RxJS的重试运算符与地图运算符结合使用时遇到一些困难。
本质上,我想做的是获取值流(在本例中为测试目的是GitHub用户),并对每个值对该值进行一些操作。
但是,我希望能够以允许我为该操作设置有限次重试的方式来处理错误。 我还假设RxJS异步处理这种事情。
Rx.Observable.fromPromise(jQuery.getJSON("https://api.github.com/users"))
.concatAll(x => Rx.Observable.of(x))
.map(x => {
if(x.id % 5 == 0) {
console.log("error");
return Rx.Observable.throw("error");
}
return x;
})
.retry(3)
.subscribe(x => console.log(x), e => console.log(e));
这是我到目前为止所能获得的,但是它应该重试相同的值3次,只打印一次“错误”,这不是我想要的行为。
我是否错过了一些琐碎的事情,或者如果用RxJS无法做到这种事情呢? 简而言之,我想异步地对值执行操作,将所述操作重新命名有限次,但允许其他有效操作并行继续。 我仅使用promise实现了类似的功能,但是代码相当混乱,我希望RxJS能让我做的事情更好看。
我看到一些可能会导致您出现问题的问题:
1)在map语句中,您通过返回一个observable进行抛出。 如果成功案例还返回了一个您随后将其展平的可观察对象(使用concat,switch或merge),这将是适当的,但是您的成功案例只会发出一个值,而不会展平。 因此,当返回Throw observable时,它永远不会被展平,因此仅被传递并记录了observable对象。
相反,您可以执行简单的javascript throw 'error'
(尽管考虑抛出错误对象而不是字符串)
2)当重试出现错误时,它将重新订阅。 如果您使用Observables进行获取,则意味着它将再次进行获取。 但是,您使用承诺进行提取,然后仅根据该承诺创建了可观察对象。 因此,当您重新订阅可观察对象时,它将最终使用与第一次相同的promise对象,并且该promise已经处于已解决状态。 不会进行其他提取。
要解决此问题,您需要具有可观察的版本来进行提取。 如果您需要从头开始制作,可能看起来像这样:
Rx.Observable.create(observer => {
jQuery.getJSON("https://api.github.com/users")
.then(result => {
observer.next(result);
observer.complete();
}, err => {
observer.error(err);
observer.complete();
});
})
尽管如果您打算经常这样做,我建议您创建一个为您完成该包装的函数。
编辑:
我的最终目标是实质上能够获取api URL数组,并使用每个URL发起get请求。 如果获取请求由于任何原因而失败,则应尝试总共进行3次以再次到达该端点。 但是,此类get请求的失败不应阻止其他请求的发生,它们应并行发生。 此外,如果get请求失败3次,则应仅将其标记为失败,并且不应从整体上停止该过程。 到目前为止,我还没有找到使用RxJS的方法。
然后我会做这样的事情:
function getJson (url) {
return Rx.Observable.create(observer) => {
jQuery.getJSON(url)
.then(result => {
observer.next(result);
observer.complete();
}, err => {
observer.error(err);
observer.complete();
});
}
}
const endpoints = ['someUrl', 'someOtherUrl'];
const observables = endpoints.map(endpoint =>
return getJson(url)
.retry(3)
.catch(err => Rx.Observable.of('error'));
});
Rx.Observable.forkJoin(...observables)
.subscribe(resultArray => {
// do whatever you need to with the results. If any of them
// errored, they will be represented by just the string 'error'
});
您的地图函数返回Rx.Observable.throw("error");
。 这是错误的,因为下面的retry()
会随后将其包装(例如,以Observable<Observable<T>>
而不是Observable<T>
进行包装。您可以使用flatMap
进行更正,但还需要通过Rx.Observable.of(x)
。
.flatMap(x => {
if(x % 5 == 0) {
console.log("error");
return Rx.Observable.throw("error");
}
return Rx.Observable.of(x);
})
.retry(3)
.subscribe(x => console.log(x), e => console.log(e));
另一种选择是throw new Error()
而不是尝试return Rx.Observable.throw("error");
确保重试正确的事情
Rx.Observable.fromPromise(jQuery.getJSON("https://api.github.com/users"))
.retry(3)
.concatAll(x => Rx.Observable.of(x))
...
编辑-为清楚起见,删除了较早失败的解决方案(某些评论可能不再相关)
要重试单个失败,您需要中断单个请求并重试那些请求。
为了正确处理错误,请针对每个预期结果将流分成子流。
我还添加了一些代码,以允许重试之一。
const Observable = Rx.Observable
// Define tests here, allow requestAgain() to pass one
const testUser = (user) => user.id % 5 !== 0
const testUserAllowOneSuccess = (user) => user.id === 5 || user.id % 5 !== 0
const requestAgain = (login) => Observable.of(login)
.switchMap(login => jQuery.getJSON("https://api.github.com/users/" + login ))
.map(x => {
if(!testUserAllowOneSuccess(x)) {
console.log("logging error retry attempt", x.id);
throw new Error('Invalid user: ' + x.id)
}
return x;
})
.retry(3)
.catch(error => Observable.of(error))
const userStream = Observable.fromPromise(jQuery.getJSON("https://api.github.com/users"))
.concatAll()
const passed = userStream.filter(x => testUser(x))
const failed = userStream.filter(x => !testUser(x))
.flatMap(x => requestAgain(x.login))
const retryPassed = failed.filter(x => !(x instanceof Error))
const retryFailed = failed.filter(x => (x instanceof Error))
.toArray()
.map(errors => { throw errors })
const output = passed.concat(retryPassed, retryFailed)
output.subscribe(
x=> console.log('subscribe next', x.id ? x.id : x),
e => console.log('subscribe error', e)
);
工作示例CodePen
然后用from
转换诺言是不可重复的
示例: https : //stackblitz.com/edit/rxjs-dp3md8
但是,使用Observable.create
效果很好
如果有人有使诺言可重复的解决方案,请发表评论。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.