RxJS Mastery – #25 race

RxJS race operator lesson title

The RxJS race operator is another one of those join creation operators. It accepts multiple Observables as input. And as the name suggests there is a race between the passed input Observables. The one that first emits is used.

RxJS race explained 🎓

Let’s imagine there are three input Observables based on the interval operator that emits every x milliseconds:

const winner$ = race(
   interval(1500),
   interval(1000),
   interval(2000),
);
winner$.subscribe();

In this case the Observable of interval(1000) emits first and is therefore used by race. The two other input Observables are ignored. In the end, winner$ is emitting every 1s. This is again demonstrated in below code where we limit the output to 5 values with take(5). We will see the numbers from 0 to 4 as well as 5 log statements for “interval(1000) won”:

'should use the winner', (done) => {
    const winner$ = race(
       interval(1500).pipe(tap(_ => console.log('interval(1500) won'))),
       interval(1000).pipe(tap(_ => console.log('interval(1000) won')), take(5)),
       interval(2000).pipe(tap(_ => console.log('interval(2000) won'))),
    );
    winner$.subscribe({ next: console.log, complete: () => done() });
});

So, as soon as the first Observable emits only that is used until it is complete. All the other Observables are unsubscribed from and ignored.

What problems does the RxJS race operator solve? 🚧

The race operator can be used if you have multiple sources that can provide the needed value. Then those resources can be triggered simultaneously and compete to deliver the value(s). This could for example make sense if you have multiple APIs where the latency is not predictable :

const response$ = race(
   ajax('http://api-a.ronnieschaniel.com/api/articles'),
   ajax('http://api-b.ronnieschaniel.com/api/articles'),
);
response$.subscribe();

Be careful if you use race and an input Observable completes but is not delivering a value. This can for example happen with the following:

it('race with of and filter', (done) => {
    const winner$ = race(
       of(1).pipe(filter(v => v > 10)),
       of(2),
       interval(1000),
    );
    winner$.subscribe({ next: console.log, complete: () => done() });
});

The of(1) is emitting and completing. The value itself is filtered out though. That’s why the winner$ also just completes, but doesn’t emit any value.
Also the following will therefore not emit any value but just a complete notification:

it('does not emit a value', (done) => {
    const winner$ = race(
        EMPTY,
        interval(1000),
    );
    winner$.subscribe({ next: console.log, complete: () => done() });
});

How to test code using the race operator🚦

Once again nothing special when testing code around the race operator. As soon as it’s clear who’s the winner one can concentrate on the output of that Observable:

it('marbles testing', () => {
    testScheduler.run((helpers) => {
        const { expectObservable } = helpers;

        const concatResult$ = race(
            interval(1000).pipe(take(3)),
            interval(2000),
        );

        expectObservable(concatResult$).toBe(
            '1s a 999ms b 999ms (c|)',
            { a: 0, b: 1, c: 2, d: 3 }
        );

    });
});

Btw. if the output happens at the same time for multiple input Observable then the one passed first in the argument list wins:

it('marbles testing with simultaneous output', () => {
    testScheduler.run((helpers) => {
        const { expectObservable, cold } = helpers;

        const concatResult$ = race(
            cold('3ms a', { a: 'first input'}),
            cold('3ms b', { b: 'second input'}),
        );

        expectObservable(concatResult$).toBe(
            '3ms a',
            { a: 'first input' }
        );

    });
});

Exercise for the race operator 💪

How can you just take one value out of the winner sequence returned by race? Is there another operator that could be used in such a case, i.e. if only the first value of many input Observables counts?

cold('-a', { a: 0}),
cold('--b', { b: 5}),

As always the code examples and exercise solution can be found on GitHub.