RxJS Mastery – #8 defer

The RxJS defer operator allows you to create an Observable lazily for each new Observer.

RxJS defer operator explained 🎓

The defer operator accepts a factory. This factory should create an Observable. Like that the factory function is only executed when an Observer subscribes to the Observable returned by defer. Hence, defer can look like this:

const observable$ = defer(() => of(1,2,3));

Let’s take the random number observable. In the case below every observer receives the same random number:

const observable$ = of(Math.floor(Math.random() * 100));

observable$.subscribe({ next: v => console.log(v) });
observable$.subscribe({ next: v => console.log(v) });
observable$.subscribe({ next: v => console.log(v) });

// output for example:
// 89
// 89
// 89

This is because the Math.random function is already executed when creating the Observable with of. Let’s see what happens if we use defer in this case:

const observable$ = defer(() => of(Math.floor(Math.random() * 100)));

observable$.subscribe({ next: v => console.log(v) });
observable$.subscribe({ next: v => console.log(v) });
observable$.subscribe({ next: v => console.log(v) });

// output for example:
// 44
// 61
// 28

As output we receive (in most cases) three different numbers. Because the Observable is created anew for each Observer.

What problems does the RxJS defer operator solve? 🚧

We have seen in the previous part what the defer operator does. But how can this help us for everyday developer tasks?

Wrap a Promise

A Promise tends to completely eagerly. Defer allows us to wrap that:

const promise = fetch('http://localhost:3000/customers');

const customers$ = defer(() => fetch('http://localhost:3000/customers'));

In the promise case the request to the customers API is executed. In the customers$ Observable case the request is not executed, but will be executed when someone subscribes to the Observable.

Dealing with Observables involving time

The defer creation operator can also be useful if your Observable is dealing with dates or times. If you require the time to be up to date on every subscription you could also apply defer:

const whenCreated$ = of(new Date());

whenCreated$.subscribe({ next: v => console.log(v) });
whenCreated$.subscribe({ next: v => console.log(v) });
whenCreated$.subscribe({ next: v => console.log(v) });


const whenSubscribed$ = defer(() => of(new Date()));

whenSubscribed$.subscribe({ next: v => console.log(v) });
whenSubscribed$.subscribe({ next: v => console.log(v) });
whenSubscribed$.subscribe({ next: v => console.log(v) });

// 2021-09-15T16:20:08.111Z
// 2021-09-15T16:20:08.111Z
// 2021-09-15T16:20:08.111Z

// 2021-09-15T16:20:08.168Z
// 2021-09-15T16:20:08.169Z
// 2021-09-15T16:20:08.170Z

In this example the last 3 outputs are logging the time when the subscription happens. This is the point in time when the async operation behind an Observable is executed.

Generally the defer operator should be used if you want to have lazy Observables.

How to test the defer operator🚦

For most of the tests with rxjs marbles defer will probably not make any difference. Below test is green:

it('should test', () => {
    testScheduler.run((helpers) => {
        const { cold, expectObservable } = helpers;

        const observable$ = cold('-1-2-3|');
        expectObservable(observable$).toBe('-a-b-c|', {a: '1', b: '2', c: '3'});

        const deferred$ = defer(() => cold('-1-2-3|'));
        expectObservable(deferred$).toBe('-a-b-c|', {a: '1', b: '2', c: '3'});
    });
});

Exercise for the defer operator 💪

For every subscriber to an Observable use defer to return:

  • an interval(1000) Observable in 50% of the cases
  • a fromEvent(document, ‘click’) Observable in the other 50% of the cases.