Skip to content
Eugene Lazutkin edited this page Jul 3, 2026 · 4 revisions

Events

double-meh's core carries a tiny synchronous event emitter — no separate module, no install step. You register string-keyed listeners with io.on, remove them with io.off, and dispatch with io.emit. All three are chainable and return io. The library uses it to report the request life cycle ('request', 'success', 'failure', 'retry', 'ready'), and you can emit your own events through it too.

import io from 'double-meh';

io.on('failure', (error, ctx) => console.error(ctx.options.url, error));
io.on('success', envelope => console.log(envelope.status));

io.on(event, fn)

Subscribe fn to event. The same function may be registered more than once (it will be called once per registration). Returns io, so calls chain.

io.on('ready', () => console.log('ready')).on('failure', err => console.error(err));

io.off(event, fn)

Unsubscribe. Removes the first registration matching fn for event by identity — so keep a reference to the exact function you passed to on. Unknown events and unknown functions are silently ignored. Returns io.

const handler = payload => console.log(payload);
io.on('update', handler);
io.off('update', handler); // must be the same reference

io.emit(event, ...args)

Dispatch event synchronously, forwarding every extra argument to each listener in registration order. Listeners run over a snapshot of the list, so a handler may safely on/off during dispatch without affecting the current pass. Returns io.

io.on('progress', (loaded, total) => {
  console.log(`${loaded}/${total}`);
});
io.emit('progress', 512, 1024); // logs "512/1024"

Life-cycle events

Every run reports itself:

  • 'request'(request, ctx) when a run starts; request is the prepared request, ctx.options the resolved options.
  • 'success'(envelope, ctx) when a run produces an envelope.
  • 'failure'(error, ctx) when a run throws (including BadStatus).
  • 'retry'({attempt, response, error}, ctx) before each attempt of the retry service; one of response / error is set depending on what triggered the retry.

A cache hit still runs the pipeline, so it emits 'request' and 'success' like a network call. Deduplicated followers of a tracked GET share the leader's single run — one 'request'/'success' pair, not one per caller.

io.inFlight counts the currently active runs — handy for a global spinner:

const spin = () => showSpinner(io.inFlight > 0);
io.on('request', spin).on('success', spin).on('failure', spin);

The 'ready' event

'ready' fires once during code-forwarding startup (installCodeForward), after any server-injected __doubleMeh prelude — queued setup callbacks, in-flight requests, and pre-arrived responses — has been drained. Use it to run code that depends on that handoff being complete.

io.on('ready', () => {
  // prelude drained; safe to issue follow-up requests
  io.get('/api/session');
});

Custom events

Beyond the wired names above, the emitter is yours: pick any other event name and use io as a lightweight app-wide bus.

io.on('auth:expired', () => io.get('/api/refresh'));

// elsewhere, e.g. from a response inspector
io.emit('auth:expired');

See also

Clone this wiki locally