Monday, November 15, 2021

RxJS - Error Handling

 The simplest way for RxJS error handling is to provide an error callback when subscribing an Observable.


Error callback in subscribe



Ex:

 
of(1, 2, 3, 4)
.pipe(
      map(
        next => {
          // Throw an error if the input value equals to 2
          if (next === 2) {
            throw new Error('My Error');
          }

          return next;
        }
      )
)
    .subscribe(
     value => console.log('Success: ' + value),
      err => console.log('Handle the error: ' + err),
      () => console.log('complete'),
);


Result:


Success: 1
  Handle the error: Error: My Error



According to the example above, we can handle errors simply in subscribe error callback, but this approach is very simple, and we cannot do much thing such as replacing the emitted value from errors or retrying it. Using catchError and retryWhen Operator, we can handle errors in Observable Stream, which make us have more controls.


Catch and Replace Approach



Using catchError, we can return a new Observable which emits a default value if there is an error out. Then, the error callback in subscribe will not be called.. 


This is the marble diagram from RxJS official Doc.


Ex: 

 
of(1, 2, 3, 4)
.pipe(
map(
next => {
// Throw an error if the input value equals to 2
if (next === 2) {
throw new Error('My Error');
}

return next;
}
),
// Return default value if it catches errors
catchError(err => of(0)),
)
.subscribe(
value => console.log('Success: ' + value),
err => console.log('Handle the error: ' + err),
() => console.log('complete'),
);


Result:


Success: 1
  Success: 0 complete



According to the result above, we can notice that the success and complete callback were triggered instead of error callback.


Catch and Rethrow Approach



We also can use catchError operator to catch the error and deal with it locally. And rethrow it to make this error be caught by error callback in subscribe. 


Ex: 

 
of(1, 2, 3, 4)
.pipe(
map(
next => {
// Throw an error if input value equals to 2
if (next === 2) {
throw new Error('My Error');
}

return next;
}
),
// Catch and deal with the error locally
// And rethrow this error
catchError(
err => {
console.log('Handle error in Observable Stream');
          return throwError(err);
}
),
)
.subscribe(
value => console.log('Success: ' + value),
err => console.log('Handle the error: ' + err),
() => console.log('complete'),
);


Result:


Success: 1 Handle error in Observable Stream Handle the error: Error: My Error



Retry



We can utilize retryWhen operator to re-subscribe the error-out Stream. 


Ex: 

 
of(1, 2, 3, 4)
.pipe(
map(
next => {
// Throw an error if input value equals to 2 (with half chance)
if (next === 2 && Math.random() > 0.5) {
throw new Error('My Error');
}

return next;
}
),
// Retry after 2 seconds if there is an error
retryWhen(
error => {
return error
.pipe(
delayWhen(() => timer(2000))
);
        }
)
)
.subscribe(
value => console.log('Success: ' + value + ' in ' +
Date.now() / 1000),
err => console.log('Handle the error: ' + err),
() => console.log('complete'),
);


Result:


Success: 1 in 1636809816.598
Success: 1 in 1636809818.613 Success: 2 in 1636809818.614 Success: 3 in 1636809818.614 Success: 4 in 1636809818.614 complete



Catch and Continue



According to this stackoverflow article, we even can re-subscribe the source Observable. 


Ex: 

 
of(1, 2, 3, 4)
.pipe(
// Using higher-order operator
switchMap(
next => of(next)
.pipe(
map(
v => {
// Throw an error if input value equals to 2
if (v === 2) {
throw new Error('My Error');
}

return next;
}
),
// Catch error and return the default value
catchError(error => of(0))
)
),
)
.subscribe(
value => console.log('Success: ' + value),
err => console.log('Handle the error: ' + err),
() => console.log('complete'),
);


Result:


Success: 1 Success: 0 Success: 3 Success: 4 complete



No comments:

Post a Comment