简体   繁体   中英

Rxjs only execute last item in queue with concatMap

I am using NGRX with RXJS. In the effects layer, I am using a concatMap to queue my requests, however, once the latest request is complete, I want to execute the last item added to the queue instead of all the remaining ones. Is this even possible?

I have tried using mergeMap, switchMap, etc, but I need to run the requests synchronously instead of concurrently. Which is why I need to use concatMap (or something similar).

    updateThing$ = createEffect(() =>
    this.actions$.pipe(
        ofType(updateThingRequest),
        concatMap((action) => {

            return this.myService
            //Once this request is complete, Instead of executing all of the pending requests from the concatMap queue,
            //I only want to execute the last request pending in the concatMap queue, if there are any, and remove all the other pending ones.
                .update(action)
                .pipe(
                    map(() => {

                        return someActionComplete();
                    }),
                    catchError((err) => {
                        return of(
                            someActionError()
                        );
                    })
                );
        })
    )
);

The problem that you have is backpressure and you want to use the "latest" backpressure strategy.

The problem is that RXJS is not a great implementation for backpressure, in RxJava this would be one line, in RXJS it will not. In versions after 4.x of RXJS, there is simply no backpressure handling, you can simulate it with some operators, but more or less that's it.

More on this: https://reactivex.io/documentation/operators/backpressure.html#collapseRxJS

So in your case, a custom operator seems like a good idea to be honest...

PS I managed to design a solution without a custom operator, which should work in theory, but the complexity is pretty high. If you are interested I will add the code here.

EDIT - Adding RXJS solution without a custom operator

The code is using two streams, one for the requests and one for the possibility to send a new request:

const requests = interval(200).pipe(take(20));
const requestCompletedStream = new BehaviorSubject(true);

combineLatest(requests, requestCompletedStream)
  .pipe(
    tap(([requestNumber]) =>
      // this will print twice for the requests that are actually executed
      console.log(`Request with number ${requestNumber} was requested`)
    ),
    filter(([_, requestComplete]) => requestComplete), // continue only when the last request has completed
    map(([requestNumber]) => requestNumber),
    distinctUntilChanged(), // do not handle the same request twice
    concatMap((requestNumber) => {
      console.log(`Async request starts handling for: ${requestNumber}`);
      requestCompletedStream.next(false);

      return of(1).pipe(
        delay(2000), // Simulates async request to the backend
        tap(() => {
          requestCompletedStream.next(true);
        }),
        map(() => requestNumber)
      );
    })
  )
  .subscribe((val) =>
    console.log(`Client received completed request with number ${val}`)
  );

You can test it here: https://stackblitz.com/edit/rxjs-backpressure-last-strategy?devtoolsheight=100&file=index.ts

The sample requests 20 requests with 200ms delay between the requests, handling of one request is 2000ms so request 0, 10 and 19 are the only one handled and passed to the subscriber.

This is visible in the console like so:

Request with number 0 was requested
Async request starts handling for: 0
Request with number 0 was requested
Request with number 1 was requested
Request with number 2 was requested
Request with number 3 was requested
Request with number 4 was requested
Request with number 5 was requested
Request with number 6 was requested
Request with number 7 was requested
Request with number 8 was requested
Request with number 9 was requested
Request with number 10 was requested
Request with number 10 was requested
Client received completed request with number 0
Async request starts handling for: 10
Request with number 10 was requested
Request with number 11 was requested
Request with number 12 was requested
Request with number 13 was requested
Request with number 14 was requested
Request with number 15 was requested
Request with number 16 was requested
Request with number 17 was requested
Request with number 18 was requested
Request with number 19 was requested
Request with number 19 was requested
Client received completed request with number 10
Async request starts handling for: 19
Request with number 19 was requested
Request with number 19 was requested
Client received completed request with number 19

You can use exhoustMap it will wait untli inner observable completes and ingore other values that will come.

https://www.learnrxjs.io/learn-rxjs/operators/transformation/exhaustmap

I'm not sure if it's correct solution but maybe you can use finalize operator? (or try to figure out proper operator by rxjs decision tree )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM