Understanding bufferWhen

The bufferWhen operator holds back values emitted by the source observable, storing them internally without forwarding them to the observer. Only when the notifier observable emits does the operator release the accumulated values as a single array. After that flush, the buffering process restarts, and data collects anew until the notifier fires again.

Conceptually, bufferWhen resembles buffer, yet there is a key distinction: following each flush, the operator unsubscribes from the notifier and only subscribes again when the next value arrives from the source.

A notable trait of bufferWhen is its ability to deliver an empty array to the observer. If the notifier emits while no values are pending in the buffer, an empty grouping is still passed along.

The operational sequence is as follows:

  1. Begin subscription to the source observable
  2. On each incoming value from the source, append it to the buffer
  3. If no active subscription to the notifier exists, initiate one
  4. When the notifier emits, forward all buffered values to the observer, irrespective of whether the buffer holds anything
  5. Terminate the subscription to the notifier
  6. Upon source completion, relay the complete notification
  7. If the source throws an error, propagate the error notification

Practical application

bufferWhen finds its place in scenarios where batching is advantageous. Consider a situation where an expensive task—like updating the DOM in response to stream events—must run repeatedly in a short span. Grouping these updates into batches allows them to be processed in a single pass, reducing overhead.

The snippet below demonstrates bufferWhen in action: it combines the latest interval values into an array that is emitted each time a click occurs:

const clicks = fromEvent(document, 'click');
const intervalEvents = interval(1000);

const buffered = intervalEvents.pipe(bufferWhen(() => clicks));

buffered.subscribe(x => console.log(x));

Interactive demo

Further reading