Postponing Emissions with delayWhen

In RxJS, delayWhen is an operator that postpones the delivery of each value emitted by the source observable. The postponement period for each value is governed by a separate observable, dubbed the duration observable. When a value arrives from the source, delayWhen invokes a callback with that value to produce the associated duration observable. The original value is then cached, and it remains in this cache until the duration observable fires. Only upon that emission does the operator forward the value to the observer.

A critical nuance is that delayWhen generates a fresh duration observable for every single value that the source emits. There is no reuse or sharing; the operator tracks the complete collection of these duration observables alongside their corresponding source values. Immediately after a duration observable emits (thus releasing its value to the observer), delayWhen unsubscribes from that specific duration observable.

The operator also accepts an optional second argument, subscriptionDelay. This is an observable that controls the timing of the source subscription. If provided, delayWhen will not subscribe to the source until subscriptionDelay emits its first value or completes. In the absence of this argument, delayWhen subscribes to the source immediately when the output observable is subscribed to.

The internal logic of delayWhen can be broken down into these steps:

  1. Subscribe to the source observable.
  2. Upon receiving a new value, call the callback to derive its unique duration observable.
  3. Store that value and its duration observable in the internal cache.
  4. Listening on each duration observable; when one emits, deliver its paired value to the observer, unsubscribe from that duration observable, and purge it from the cache.
  5. Propagates the completion signal to the observer once the source has finished.
  6. Propagates the error signal to the observer if the source throws an error.

Practical Applications

Consider scenarios that demand waiting for a side-effect to finish before exposing a value downstream. For instance, after obtaining a user's login token, you might need to persist that token offline before handing it over to the rest of the application. delayWhen is uniquely suited for such delays.

The following contrived example shows how to postpone sending a token until it has been safely stored in local storage:

login().pipe(
   delayWhen((user) => setToLocalStorage(user))
).subscribe((user) => console.log(user));

function login() {
   return fromFetch('https://reqres.in/api/users/2').pipe(
       switchMap((response) => response.json())
   );
}

function setToLocalStorage(value) {
   return of(value).pipe(
       tap((value) => {
           localStorage.setItem('token', JSON.stringify(value));
       })
   );
}

Persisting to local storage serves as an illustrative stand-in; the callback can execute any synchronous or asynchronous task in a real-world context.

Interactive Playground