From b902979ac0a7e3ec8f4266033c4cb4560711cc1b Mon Sep 17 00:00:00 2001 From: Szymon Marczak <36894700+szmarczak@users.noreply.github.com> Date: Sun, 18 Nov 2018 10:11:41 +0100 Subject: [PATCH] Support waiting for multiple event emissions (#15) Fixes #3 --- index.js | 58 +++++++++++++++++++++++++++++++++++++++++-------------- readme.md | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++ test.js | 40 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 14 deletions(-) diff --git a/index.js b/index.js index da94670..abc2346 100644 --- a/index.js +++ b/index.js @@ -17,50 +17,59 @@ const normalizeEmitter = emitter => { }; }; -module.exports = (emitter, event, options) => { +const multiple = (emitter, event, options) => { let cancel; - const ret = new Promise((resolve, reject) => { - if (typeof options === 'function') { - options = {filter: options}; - } - options = Object.assign({ rejectionEvents: ['error'], - multiArgs: false + multiArgs: false, + resolveImmediately: false }, options); + if (!(options.count >= 0 && (options.count === Infinity || Number.isInteger(options.count)))) { + throw new TypeError('The `count` option should be at least 0 or more'); + } + + const items = []; const {addListener, removeListener} = normalizeEmitter(emitter); - const resolveHandler = (...args) => { + const onItem = (...args) => { const value = options.multiArgs ? args : args[0]; if (options.filter && !options.filter(value)) { return; } - cancel(); - resolve(value); + items.push(value); + + if (options.count === items.length) { + cancel(); + resolve(items); + } }; - const rejectHandler = (...args) => { + const rejectHandler = error => { cancel(); - reject(options.multiArgs ? args : args[0]); + reject(error); }; cancel = () => { - removeListener(event, resolveHandler); + removeListener(event, onItem); for (const rejectionEvent of options.rejectionEvents) { removeListener(rejectionEvent, rejectHandler); } }; - addListener(event, resolveHandler); + addListener(event, onItem); for (const rejectionEvent of options.rejectionEvents) { addListener(rejectionEvent, rejectHandler); } + + if (options.resolveImmediately) { + resolve(items); + } }); ret.cancel = cancel; @@ -73,6 +82,27 @@ module.exports = (emitter, event, options) => { return ret; }; + +module.exports = (emitter, event, options) => { + if (typeof options === 'function') { + options = {filter: options}; + } + + options = Object.assign({}, options, { + count: 1, + resolveImmediately: false + }); + + const arrayPromise = multiple(emitter, event, options); + + const promise = arrayPromise.then(array => array[0]); + promise.cancel = arrayPromise.cancel; + + return promise; +}; + +module.exports.multiple = multiple; + module.exports.iterator = (emitter, event, options) => { if (typeof options === 'function') { options = {filter: options}; diff --git a/readme.md b/readme.md index c057462..6cd6d35 100644 --- a/readme.md +++ b/readme.md @@ -144,6 +144,62 @@ const emitter = require('./some-event-emitter'); })(); ``` +### pEvent.multiple(emitter, event, options) + +Wait for multiple event emissions. Returns an array. + +This method has the same arguments and options as `pEvent()` with the addition of the following options: + +#### options + +Type: `Object` + +##### count + +*Required*
+Type: `number` + +The number of times the event needs to be emitted before the promise resolves. + +##### resolveImmediately + +Type: `boolean`
+Default: `false` + +Whether to resolve the promise immediately. Emitting one of the `rejectionEvents` won't throw an error. + +**Note**: The returned array will be mutated when an event is emitted. + +Example: + +```js +const emitter = new EventEmitter(); + +const promise = pEvent.multiple(emitter, 'hello', { + resolveImmediately: true, + count: Infinity +}); + +const result = await promise; +console.log(result); +//=> [] + +emitter.emit('hello', 'Jack'); +console.log(result); +//=> ['Jack'] + +emitter.emit('hello', 'Mark'); +console.log(result); +//=> ['Jack', 'Mark'] + +// Stops listening +emitter.emit('error', new Error('😿')); + +emitter.emit('hello', 'John'); +console.log(result); +//=> ['Jack', 'Mark'] +``` + ### pEvent.iterator(emitter, event, [options]) ### pEvent.iterator(emitter, event, filter) diff --git a/test.js b/test.js index 0e33f9e..3704839 100644 --- a/test.js +++ b/test.js @@ -262,3 +262,43 @@ test('resolve event resolves pending promises and finishes the iterator', async await t.deepEqual(await iterator.next(), {done: true, value: undefined}); }); + +test('.multiple()', async t => { + const emitter = new EventEmitter(); + + const promise = m.multiple(emitter, '🌂', { + count: 3 + }); + + emitter.emit('🌂', '🌞'); + emitter.emit('🌂', '🌞'); + emitter.emit('🌂', '🌞'); + emitter.emit('🌂', '🌞'); + + t.deepEqual(await promise, ['🌞', '🌞', '🌞']); +}); + +test('.multiple() - `resolveImmediately` option', async t => { + const emitter = new EventEmitter(); + + const promise = m.multiple(emitter, '🌂', { + resolveImmediately: true, + count: Infinity + }); + + const result = await promise; + t.deepEqual(result, []); + + emitter.emit('🌂', '🌞'); + emitter.emit('🌂', '🌞'); + emitter.emit('🌂', '🌞'); + emitter.emit('🌂', '🌞'); + + t.deepEqual(result, ['🌞', '🌞', '🌞', '🌞']); +}); + +test('`count` option should be a zero or more', async t => { + await t.throws(m.multiple(null, null, { + count: -1 + }), 'The `count` option should be at least 0 or more'); +});