How to send request sequently with checks before each request?

522 Views Asked by At

I need:

  • send N requests sequently (I use concatMap to do this)
  • only if object is valid (I extend concat map with validation code)
  • stop queue on first error (by server error response or by client check rejection)

Now the code is look like:

ids = [{valid: true, id: 1}, {valid: true, id: 2}, {valid: true, id: 3}, {valid: false, id: 4}]
constructor(httpClient: HttpClient) {
  from(this.ids)
  .pipe(
    map(obj => ({...obj, id: obj.id + 5})),
    concatMap(obj => {
      console.log('concat map');
      if (obj.valid === false) {
        return throwError('not valid');
      } else {
        return httpClient.get(`https://jsonplaceholder.typicode.com/todos/${obj.id}`);
      }
    })
  )
  .subscribe(
    result => console.log('success', result),
    result => console.log('error', result),
    () => console.log('complete')
  );
}

Here is stackblitz: https://stackblitz.com/edit/angular-5vwbjg

So what I want is to not to send the request if object is not valid. And I want to process next id from initial observable, only if handling of previous is ended. So I want flow like:

  1. map + concatMap on first id
  2. map + concatMap on second id
  3. and more more more...

I handled this only by extending concatMap code. Is it somehow possible to extract this check to another pipeable operator before the concatMap? So concatMap body will be simpler.

I tried mergeMap operator like:

mergeMap(obj => {
  console.log(obj);
  if (obj.valid === false) {
    return throwError('not valid');
  } else {
    return of(obj);
  }
}),

And I got this in console:

enter image description here

This is not correct result. I expect 3 requests and then 1 error. Expected result:

enter image description here

Also maybe there is more beautiful way to describe check function in my case? Any ideas? Without that if with 2 branches.

I think that I misunderstood that point. from(this.ids) emits values anyway. It does not wait concatMap or map or any other operator. On that moment, when new value is emitted in from(this.ids) (it will be before concatMap finishes) it proccess whole chain: map + concatMap. What I need is somehow say rxjs to process 2nd emitted value, after concatMap finished handling 1st. Then it should process 3rd emitted value, after concatMap finished handling 2nd e t c.

A bit extended example is here: https://stackblitz.com/edit/angular-ovffm4?file=src/app/app.component.ts

Here I have 2 client checks and 1 server response.

Here is what I see in console:

map
first check true
second check 6
make request
map
first check true
second check 7
map
first check true
second check 8
map
first check true
second check 9
map
first check true
second check 10
map
first check false
success {userId: 1, id: 6, title: "qui ullam ratione quibusdam voluptatem quia omnis", completed: false}
make request
success {userId: 1, id: 7, title: "illo expedita consequatur quia in", completed: false}
complete

How to change my observable so it will be like:

map
first check true
second check 6
make request
success {userId: 1, id: 6, title: "qui ullam ratione quibusdam voluptatem quia omnis", completed: false}
map
first check true
second check 7
make request
success {userId: 1, id: 7, title: "illo expedita consequatur quia in", completed: false}
map
first check true
second check 8
map
first check true
second check 9
map
first check true
second check 10
map
first check false
complete
1

There are 1 best solutions below

8
On

For starters, i think you misunderstood how error handling works in rxjs. The error handling function you pass as 2nd parameter to the subscribe function will never be called more than once. Once an observable encounters one single error, it is completed and no longer functions. So you can not use that function to catch multiple errors.

http://reactivex.io/documentation/operators/subscribe.html

An Observable calls this method to indicate that it has failed to generate the expected data or has encountered some other error. This stops the Observable and it will not make further calls to onNext or onCompleted. The onError method takes as its parameter an indication of what caused the error (sometimes an object like an Exception or Throwable, other times a simple string, depending on the implementation).

You will never be able to handle it this way.

This might be an alternative way:

https://stackblitz.com/edit/angular-wbu3tv

Snippet:

  concatMap(obj => {
    console.log(obj);
    if (obj.valid === false) {
      return of({
          response: null,
          success: false
        });
    } else {
      return httpClient.get(`https://jsonplaceholder.typicode.com/todos/${obj.id}`).pipe(map(response => {
        return {
          response: response,
          success: true
        }
      }));
    }
  })

So basically, instead of using throwError, i return an object that indicates wether a request was executed, and the response if it was executed.