Skip to content

Commit

Permalink
Support waiting for multiple event emissions (#15)
Browse files Browse the repository at this point in the history
Fixes #3
  • Loading branch information
szmarczak authored and sindresorhus committed Nov 18, 2018
1 parent be3fcbd commit b902979
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 14 deletions.
58 changes: 44 additions & 14 deletions index.js
Expand Up @@ -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;
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
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

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

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

0 comments on commit b902979

Please sign in to comment.