abacus-events
Version:
Iterate over a stream of Node events
119 lines (101 loc) • 3.69 kB
JavaScript
;
// Small utility functions to help work with Node events, including an event
// emitter that can be shared between modules in a process, and an iterator
// over a stream of events.
// We use a non camel case variable name intentionally here
/* eslint camelcase:1 */
const _ = require('underscore');
const events = require('events');
const emitStream = require('emit-stream');
const through = require('through');
const first = _.first;
const rest = _.rest;
// Don't limit the number of listeners
events.EventEmitter.defaultMaxListeners = 0;
// Return an emitter, if a name is provided the emitter is shared
const emitter = (name) => {
if (name) return shared(name);
const e = new events.EventEmitter();
// Don't limit the number of listeners
e.setMaxListeners(0);
return e;
};
// Return a shared, named, event emitter
if (!global.__shared_emitters) global.__shared_emitters = [];
const shared = (name) => {
const e = global.__shared_emitters[name];
if (e) return e;
const ne = emitter();
global.__shared_emitters[name] = ne;
return ne;
};
// Convert a Node event emitter to a pausable Node stream. Events are buffered
// while the stream is paused.
const stream = (emitter) => {
// Create a stream 'through' transform that can be paused, buffers messages
// while it's paused, then delivers them when it's resumed
const pausable = through((data) => pausable.queue(data[0] ? data : null), () => pausable.queue(null));
// Turn the given emitter into an initially paused emit stream
const stream = emitStream.toStream(emitter).pipe(pausable);
stream.pause();
return stream;
};
// Turn a Node event emitter into an async iterator. The iterator's next method
// takes a callback. The callback will be called with the next emitted event.
const iterator = (emitter) => {
// First turn the emitter into a Node stream
const nstream = stream(emitter);
// Maintain a queue of the callbacks that we're expected to call with the
// next events from the emitter
// Warning: callbacks is a mutable variable
let callbacks = [];
// Dequeue and return the first callback in the queue
const callback = () => {
// Warning: mutating variable callbacks here
const cb = first(callbacks);
callbacks = rest(callbacks);
return cb;
};
// Pipe the Node stream into a 'through' transform that captures events
// and passes them to the callbacks
nstream.pipe(
through(
(data) => {
// We got some data representing an event from the event emitter
// Pause the stream now if we only have one callback in the queue
if (callbacks.length === 1) nstream.pause();
// Pass the event to the callback, we also convert from the [name, val]
// produced by emit-stream to a { name: ..., value: ... } object
callback()(undefined, {
value: {
name: data[0],
value: data[1]
},
done: false
});
},
() => {
// End of the stream, signalled by a null event, call the first
// callback in the queue with done true
callback()(undefined, {
value: undefined,
done: true
});
}
)
);
// Return an iterator
const it = {};
it.next = (cb) => {
// The caller wants to be called back with the next event, add the
// callback to our queue and resume the Node stream to get the next
// available event
callbacks = callbacks.concat([cb]);
setImmediate(() => nstream.resume());
};
return it;
};
// Export our public functions
module.exports.emitter = emitter;
module.exports.stream = stream;
module.exports.iterator = iterator;