用RXJS实现http请求队列
我对RXJS还是个新手,正在努力了解它。任务如下: 用户可以将资产添加到集合中,其单击速度可能快于后端处理请求的速度,因此可能会发生错误,因为集合的锁定版本与客户端发送的锁定版本冲突(尚未更新)。一个老套的解决方案是在409错误发生时重新加载集合,但我认为更优雅的解决方案是请求队列 此外,当两个用户正在处理同一个集合时,我们将不得不处理409个合理错误,这样队列就会派上用场(重新加载集合,将项目重新添加到队列) 以下是我的天真方法,它实际上是有效的:用RXJS实现http请求队列,rxjs,queue,Rxjs,Queue,我对RXJS还是个新手,正在努力了解它。任务如下: 用户可以将资产添加到集合中,其单击速度可能快于后端处理请求的速度,因此可能会发生错误,因为集合的锁定版本与客户端发送的锁定版本冲突(尚未更新)。一个老套的解决方案是在409错误发生时重新加载集合,但我认为更优雅的解决方案是请求队列 此外,当两个用户正在处理同一个集合时,我们将不得不处理409个合理错误,这样队列就会派上用场(重新加载集合,将项目重新添加到队列) 以下是我的天真方法,它实际上是有效的: export class RequestQu
export class RequestQueue<T> {
private items: T[] = [];
private queue$: Subject<boolean> = new Subject<boolean>();
private dispatcher: Subject<T> = new Subject<T>();
constructor(
private readonly requestHandler: (v: T) => Observable<unknown>,
private readonly errorHandler: (e: HttpErrorResponse) => Observable<boolean>
) {
this.initQueue();
}
public add(item: T) {
if (this.items.push(item) == 1) {
this.dispatcher.next(this.items.shift());
}
return this.queue$;
}
public destroy () {
this.dispatcher.complete();
}
private initQueue() {
this.dispatcher.pipe(
flatMap(item => {
return this.requestHandler(item)
}),
tap(r => {
if (this.items.length > 0) {
this.dispatcher.next(this.items.shift());
} else {
this.queue$.next(true);
}
}),
catchError((error: HttpErrorResponse) => {
let error$ = this.errorHandler(error);
this.initQueue(); //<--- please note this line
return error$;
})
).subscribe();
}
}
导出类请求队列{
私人项目:T[]=[];
专用队列$:Subject=new Subject();
私有调度程序:Subject=新Subject();
建造师(
私有只读请求处理程序:(v:T)=>可观察,
私有只读errorHandler:(e:HttpErrorResponse)=>可观察
) {
this.initQueue();
}
公共地址(项目:T){
if(this.items.push(item)==1){
this.dispatcher.next(this.items.shift());
}
返回此.queue$;
}
公开销毁(){
this.dispatcher.complete();
}
私有initQueue(){
这是一根管子(
平面图(项目=>{
返回此.requestHandler(项)
}),
点击(r=>{
如果(this.items.length>0){
this.dispatcher.next(this.items.shift());
}否则{
this.queue$.next(true);
}
}),
catchError((错误:HttpErrorResponse)=>{
让error$=this.errorHandler(error);
this.initQueue();//尝试以下操作:在requestHandler
上捕获错误,而不是在调度管道中捕获错误。如果调度管道接收到错误,则将执行此操作。如果requestHandler
捕获错误而不抛出新错误,则该请求将终止,但调度程序将继续生存
作为简短的旁白:
您可能会从使用concatMap
中受益concatMap
在前一个完成之前不会订阅下一个可观察对象,这正是您所描述的行为
constructor(/* ... */) {
this.dispatcher.pipe(
concatMap(item => this.requestHandler(item))
).subscribe();
}
现在您根本不需要队列,因为concatMap一次只处理一个请求
返回错误处理:
一旦流发出错误或完整信号,它就会完成。因此,在失败时重新创建请求确实是一种方法。您可以使用catchError手动执行此操作,也可以使用retry和/或retryWhen来执行此操作。理想情况下,您可以同时使用这两种方法。在这里,我将重试请求5次,如果请求仍然失败,我将放弃并删除请求,然后重试其余的继续
constructor(/* ... */) {
this.dispatcher.pipe(
concatMap(item => this.requestHandler(item).pipe(
retry(5),
catchError(err => {
// Here is the place to alert the user to the error or handle it however
// you like.
// empty() will complete immediately, dropping this request and
// telling concatMap that it can just start the next request instead.
// - You can throw an error here to cancel all pending requests
// - You can convert the item into a placeholder item so that the user
// can re-try adding an asset on their own leisure. (If you build that
// capability into the UI)
return EMPTY;
})
))
).subscribe();
}
现在,requestHandler(item)
最多将被调用五次。如果第六次失败,请求将被删除,concatMap将移动到下一次。当然,您可以将其更改为任何您喜欢的内容。retryWhen()
允许您决定在何种条件下重试
在catchError
之后,源可观察对象已失效,但返回的任何可观察对象都将继续。您可以随意操纵此行为。尝试以下操作:在requestHandler
而不是在调度管道中捕获错误。如果调度管道接收到错误,则将完成此操作。如果requestHandler
捕获一个错误并且不抛出新的错误,那么该请求将终止,但调度程序将继续
作为简短的旁白:
您可能会从使用concatMap
中受益concatMap
在前一个完成之前不会订阅下一个可观察对象,这正是您所描述的行为
constructor(/* ... */) {
this.dispatcher.pipe(
concatMap(item => this.requestHandler(item))
).subscribe();
}
现在您根本不需要队列,因为concatMap一次只处理一个请求
返回错误处理:
一旦流发出错误或完整信号,它就会完成。因此,在失败时重新创建请求确实是一种方法。您可以使用catchError手动执行此操作,也可以使用retry和/或retryWhen来执行此操作。理想情况下,您可以同时使用这两种方法。在这里,我将重试请求5次,如果请求仍然失败,我将放弃并删除请求,然后重试其余的继续
constructor(/* ... */) {
this.dispatcher.pipe(
concatMap(item => this.requestHandler(item).pipe(
retry(5),
catchError(err => {
// Here is the place to alert the user to the error or handle it however
// you like.
// empty() will complete immediately, dropping this request and
// telling concatMap that it can just start the next request instead.
// - You can throw an error here to cancel all pending requests
// - You can convert the item into a placeholder item so that the user
// can re-try adding an asset on their own leisure. (If you build that
// capability into the UI)
return EMPTY;
})
))
).subscribe();
}
现在,requestHandler(item)
最多将被调用五次。如果第六次失败,请求将被删除,concatMap将移动到下一次。当然,您可以将其更改为任何您喜欢的内容。retryWhen()
允许您决定在何种条件下重试
在catchError
之后,源可观察对象已死亡,但返回的任何可观察对象都会继续。您可以随意操纵此行为。另一种方法是让您的添加
函数(我将其命名为请求
)返回一个实际发出http请求发出的内容并可用于取消请求的可观察对象
private dispatcher:Subject=new Subject();
公开请求(项目:T){
返回新的可观察对象(观察者=>{
const cancel=新主题();
const request$=此.requestHandler(项).pipe(
catchError(此.errorHandler),
takeUntil(cancel),//取消取消事件的请求
轻触(观察者)//将观察者附加到请求
));
this.dispatcher.next(request$)//将请求添加到队列中
return()=>{//取消订阅时发送取消事件
取消。下一步();
取消。完成();
}
});
}
私有initQueue(){
this.dispatcher.pipe(concatAll()).subscribe();
}
像这样使用它
cancel=新主题();
发送(项目:T){
请求(项目)。管道(
takeUntil(这个。取消)
).subscribe()
}
取消所有(){
this.cancel.next()
}
另一种方法是让您的add
函数(我称之为request
)返回一个实际发出http