Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support waiting for multiple event emissions #15

Merged
merged 16 commits into from
Nov 18, 2018
58 changes: 44 additions & 14 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,50 +17,59 @@ const normalizeEmitter = emitter => {
};
};

module.exports = (emitter, event, options) => {
const multiple = (emitter, event, options) => {
szmarczak marked this conversation as resolved.
Show resolved Hide resolved
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;
Expand All @@ -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};
Expand Down
56 changes: 56 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

szmarczak marked this conversation as resolved.
Show resolved Hide resolved
*Required*<br>
Type: `number`

The number of times the event needs to be emitted before the promise resolves.

##### resolveImmediately

Type: `boolean`<br>
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)

Expand Down
40 changes: 40 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
szmarczak marked this conversation as resolved.
Show resolved Hide resolved
});

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