RxJS Mastery – #17 throwError

RxJS throwError operator lesson title

The RxJS throwError operator helps you to throw an error. This can be useful when you want to create an Observable that emits an error. However, using throwError inside an operator or creation function is usually not necessary.

RxJS throwError explained 🎓

The throwError operator creates an Observable like all other creation operators. But in this case the created Observable will create and push an error to the Observer. A next notification is not emitted. This is also indicated by the return type which is Observable<never>.

throwError(errorOrErrorFactory: any, scheduler?: SchedulerLike): Observable<never>

The following Observable will just throw an error and log nothing to the console:

throwError(1).subscribe({ next: console.log });

Because no value is emitted via next notification, there is also nothing reaching the console log function. Let’s extend this a bit:

it('should be handled in the observer', () => {
    throwError(1).subscribe({
        next: console.log,
        error: (e) => console.log('Error thrown: ' + e),
        complete: () => console.log('complete'),
    });
});
//     Error thrown: 1

If we pass an Observer to the subscribe function that also implements the error function we get something in the console. The thrown error is logged. No next nor complete notification is emitted. If an Observable has an error the operations stop and no other values can be sent.

What problems does the RxJS throwError operator solve? 🚧

Inside most operators the throwError operator is not really needed. If you want to throw errors in the inner return of, e.g. concatMap, mergeMap, defer, and many others, you can simply throw the error. RxJS will consider it as an error notification and notify the consumer, i.e. observer.

const $error = of(1,2).pipe(
    concatMap(value => {
        if (value === 2) {
            throw Error('x was 2!');
        }
        return of(value);
    })
);

Above Observable $error will first emit the value 1. After that it will send an error notification.

Hence, the throwError operator is useful in cases where you’re not in an inner Observable context. If you just need to throw an error in part of your code and want that error to be handled in a reactive way.

How to test the throwError operator🚦

Also in the error case rxjs marbles can be used to assert that an error is thrown and also check the error value. In the example below we directly subscribe to throwError. In our case the error that is thrown is just the number 1. The toBe function accepts for such error cases three parameters.

  • In the diagram string we pass # to denote an error.
  • The second parameters accepts the value that are emitted by next notifications. In our case the error is thrown immediately and no next notification is sent. Therefore this parameter’s value is null.
  • Finally the third parameter is the errorValue, i.e. the error that was thrown. In our case it’s 1. Which does not make that much sense. Normally you would emit an error instance, e.g. by using new Error(‘Something went wrong’).
it('should emit with error', () => {
    testScheduler.run((helpers) => {
        const { expectObservable } = helpers;

        expectObservable(throwError(1)).toBe('#', null, 1);
    });
});

We can also test Observables that first emit values before an error is sent. Let’s take above example with the concatMap that first sends the value 1 and then runs into an error.

it('should emit an error without throwError', () => {
    testScheduler.run((helpers) => {
        const { expectObservable } = helpers;

        const $error = of(1,2).pipe(
            concatMap(value => {
                if (value === 2) {
                    throw Error('x was 2!');
                }
                return of(value);
            })
        );

        expectObservable($error).toBe('(1#)',{'1': 1}, new Error('x was 2!'));
    });
});

Exercise for the throwError operator 💪

Use multiple operators in a pipe and let the first operator throw an error. Show in a test what happens to the rest of the code? Are the subsequent operators executed?