Batch Event FlusherLoading saved progress…

Batch Event Flusher

A batch event flusher buffers a stream of events and delivers them together, flushing early when the buffer reaches a size limit or after a fixed time has passed since the batch's first event, whichever comes first. It is the pattern behind analytics SDKs and logging clients that would rather send one network request per hundred events (or per second) than one request per event.

Implement batchEventFlusher(onFlush, { maxSize, maxWait }). It returns { push, flush }. Calling push(event) adds an event to the current batch. When the batch reaches maxSize, onFlush is called synchronously with the buffered events, in order. Otherwise the batch flushes maxWait ms after its first event. Calling flush() flushes the current batch now (if any) and cancels the pending timer.

Signature

function batchEventFlusher<T>(
  onFlush: (events: T[]) => void,
  options: { maxSize: number; maxWait: number }
): { push: (event: T) => void; flush: () => void };

Examples

// Size trigger: the batch fills up.
const flusher = batchEventFlusher(
  (events) => console.log('flush', events),
  { maxSize: 3, maxWait: 1000 }
);
flusher.push('a');
flusher.push('b');
flusher.push('c'); // reaches maxSize → logs: flush ['a','b','c'] (synchronously)
// Time trigger: the batch never fills, so the timer fires.
const flusher = batchEventFlusher(
  (events) => console.log('flush', events),
  { maxSize: 100, maxWait: 500 }
);
flusher.push('x'); // opens a window; the maxWait timer starts now
flusher.push('y'); // joins the same window; the timer is NOT restarted
// 500ms after the FIRST push: logs flush ['x','y']

Notes

  • Size wins immediately — the moment the batch reaches maxSize, call onFlush synchronously, in the same tick as the push that filled it. Do not wait for the timer.
  • The timer starts on the first push of a batch — not on every push. Later pushes in the same window must not restart it, so a steady stream still flushes every maxWait ms rather than never.
  • flush() is manual — it flushes the current batch now if it holds anything and cancels the pending timer. On an empty batch it does nothing.
  • Every flush resets the batch — after a size flush, a time flush, or a manual flush, the buffer is empty and the next push opens a fresh window with a fresh timer.
  • Order is preservedonFlush receives events oldest-first, in the order they were pushed.
  • Don't worry about an onFlush that throws, or overlapping async calls — assume single-threaded calls and a well-behaved callback.

FAQ

How is this different from debounce or throttle?
Debounce restarts its timer on every call and only fires after a quiet gap, so a continuous stream never fires. A batch flusher starts its timer once per batch (on the first event) and also flushes early on a size cap, so a steady stream still flushes on a fixed cadence.
Why start the timer on the first event instead of on every push?
So a steady stream can't push the deadline forever. Anchoring the timer to the first event of a batch guarantees every buffered event is delivered within maxWait of the batch opening, not maxWait after the stream finally pauses.
What happens to the pending timer when a size flush or manual flush happens first?
It is cancelled. Any flush clears the current timer and empties the batch, and the next push opens a brand-new window with its own fresh timer, so a stale timer can never flush the following batch early.
Loading editor…