Search code examples
angularerror-handlingrxjs

Using rxjs, can I continue after catching an error?


What I want to do is the following:

  • I have X number of function calls containing an rxjs observable chain that I want to run sequentially
  • If I get an error in part of the observable chain, what I want is to pop up a dialog and ask if the user wants to continue or stop.
  • If they want to stop, it's easy and everything stops
  • But if they want to continue, I want to keep on going

I originally tried to do it in the catchError inside the function, but that didn't work...it kept on going no matter what. Then I thought to throw it to the error in the subscribe and that mostly works except that I can't continue.

Any hope please? Trying to wrap my head around rxjs and I'm learning a lot...but don't know if I can get around this.

Here's a stackblitz.

export class AppComponent {
  constructor(private postService: PostService, public dialog: MatDialog) {}

  startEverything(): void {
    const sameFunc = (id1: number, id2: number) => {
      return this.postService.addPost(id1).pipe(
        switchMap((post: Post) => {
          return this.postService.getPosts().pipe(take(1));
        }),
        switchMap((posts: Post[]) =>
          this.postService.getPost(id2).pipe(
            map((post) => post),
            // catch any errors here and throw to down below
            catchError((error) => {
              console.log('error: ', error);
              throw new Error(error);
            })
          )
        ),
        switchMap((queryId: string) =>
          this.postService.deletePost(9).pipe(catchError(() => of(undefined)))
        )
      );
    };

    // creating an array of three function calls, second one will fail
    const requests = [sameFunc(1, 1), sameFunc(2, 999), sameFunc(3, 3)];

    from(requests)
      .pipe(
        // run three function calls sequentially
        concatAll()
      )
      .subscribe({
        next: (x) => console.log('Observer got a next value: ' + x),
        error: (err) => {
          console.error('Observer got an error: ' + err);
          // pop up dialog here with STOP/CONTINUE
          const dialogRef = this.dialog.open(DialogContentExampleDialog);

          dialogRef.afterClosed().subscribe((result) => {
            console.log(`Dialog result: ${result}`);
            if (result) {
              // continue with next concatAll() <== can I do this??
            }
          });
        },
        complete: () => {
          console.log('Observer got a complete notification');
        },
      });
  }
}

EDIT: Ah okay, I just realized that the order isn't happening the way I wanted. What I want is this:

* post1
* get1
* delete1
* post2
* get2 <--- this fails
* pop up dialog
===================
* if continue
* delete2
* post3
* get3
* delete3
===================
* if stop
* delete2

And there can be any number of posts, not just 3 and any can fail on the get. Is this possible?


Solution

  • Probably there is a better way but you could handle your scenario with the following example:

    startAll(): void {
      const first$ = of(1);
      const third$ = of(3);
      const fourth$ = of(4);
      const second$ = of(2).pipe(
        map((val) => {
          if (val === 2) throw new Error('an error occurred...');
          return val;
        })
      );  
      
      this._handleRequests([first$, second$, third$, fourth$]);
    }
    
    
    private _handleRequests(requests: Observable<number>[]): void {
      let _continue = true;
    
      from(requests)
        .pipe(
          concatMap((obs$) =>
            obs$.pipe(
              mergeMap((value) => of(value)),
              catchError(() =>
                this._handleDialog().pipe(tap((v) => (_continue = v)))
              ),
              takeWhile(() => _continue === true)
            )
          )
        )
        .subscribe((v) => console.log('Subscribed ', v));
    }
    
    private _handleDialog(): Observable<boolean> {
     const dialogRef = this.dialog.open(DialogContentExampleDialog);
     return dialogRef.afterClosed();
    }
    

    With the rxjs concatMap operator, you wait for the observable to complete in order to handle the next one, so it executes sequentially. Additionally, by making use of takeWhile, you could set the necessary condition for stopping the whole emitted values, so in this, the condition is given by the user through the material dialog.

    The output for when the user decides to continue would be:

    // Subscribed 1
    // Subscribed true
    // Subscribed 3
    // Subscribed 4
    

    When the user decides to stop, then the output will be:

    // Subscribed 1