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');
+});