用RXJS实现http请求队列

用RXJS实现http请求队列,rxjs,queue,Rxjs,Queue,我对RXJS还是个新手,正在努力了解它。任务如下: 用户可以将资产添加到集合中,其单击速度可能快于后端处理请求的速度,因此可能会发生错误,因为集合的锁定版本与客户端发送的锁定版本冲突(尚未更新)。一个老套的解决方案是在409错误发生时重新加载集合,但我认为更优雅的解决方案是请求队列 此外,当两个用户正在处理同一个集合时,我们将不得不处理409个合理错误,这样队列就会派上用场(重新加载集合,将项目重新添加到队列) 以下是我的天真方法,它实际上是有效的: export class RequestQu

我对RXJS还是个新手,正在努力了解它。任务如下:

用户可以将资产添加到集合中,其单击速度可能快于后端处理请求的速度,因此可能会发生错误,因为集合的锁定版本与客户端发送的锁定版本冲突(尚未更新)。一个老套的解决方案是在409错误发生时重新加载集合,但我认为更优雅的解决方案是请求队列

此外,当两个用户正在处理同一个集合时,我们将不得不处理409个合理错误,这样队列就会派上用场(重新加载集合,将项目重新添加到队列)

以下是我的天真方法,它实际上是有效的:

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