Skip to content

Commit df55731

Browse files
mcollinatargos
authored andcommittedMar 27, 2019
events: add once method to use promises with EventEmitter
This change adds a EventEmitter.once() method that wraps ee.once in a promise. Co-authored-by: David Mark Clements <david.mark.clements@gmail.com> PR-URL: #26078 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com>
1 parent 51d874c commit df55731

File tree

3 files changed

+164
-0
lines changed

3 files changed

+164
-0
lines changed
 

‎doc/api/events.md

+41
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,47 @@ newListeners[0]();
653653
emitter.emit('log');
654654
```
655655

656+
## events.once(emitter, name)
657+
<!-- YAML
658+
added: REPLACEME
659+
-->
660+
* `emitter` {EventEmitter}
661+
* `name` {string}
662+
* Returns: {Promise}
663+
664+
Creates a `Promise` that is resolved when the `EventEmitter` emits the given
665+
event or that is rejected when the `EventEmitter` emits `'error'`.
666+
The `Promise` will resolve with an array of all the arguments emitted to the
667+
given event.
668+
669+
```js
670+
const { once, EventEmitter } = require('events');
671+
672+
async function run() {
673+
const ee = new EventEmitter();
674+
675+
process.nextTick(() => {
676+
ee.emit('myevent', 42);
677+
});
678+
679+
const [value] = await once(ee, 'myevent');
680+
console.log(value);
681+
682+
const err = new Error('kaboom');
683+
process.nextTick(() => {
684+
ee.emit('error', err);
685+
});
686+
687+
try {
688+
await once(ee, 'myevent');
689+
} catch (err) {
690+
console.log('error happened', err);
691+
}
692+
}
693+
694+
run();
695+
```
696+
656697
[`--trace-warnings`]: cli.html#cli_trace_warnings
657698
[`EventEmitter.defaultMaxListeners`]: #events_eventemitter_defaultmaxlisteners
658699
[`domain`]: domain.html

‎lib/events.js

+30
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ function EventEmitter() {
2727
EventEmitter.init.call(this);
2828
}
2929
module.exports = EventEmitter;
30+
module.exports.once = once;
3031

3132
// Backwards-compat with node 0.10.x
3233
EventEmitter.EventEmitter = EventEmitter;
@@ -482,3 +483,32 @@ function unwrapListeners(arr) {
482483
}
483484
return ret;
484485
}
486+
487+
function once(emitter, name) {
488+
return new Promise((resolve, reject) => {
489+
const eventListener = (...args) => {
490+
if (errorListener !== undefined) {
491+
emitter.removeListener('error', errorListener);
492+
}
493+
resolve(args);
494+
};
495+
let errorListener;
496+
497+
// Adding an error listener is not optional because
498+
// if an error is thrown on an event emitter we cannot
499+
// guarantee that the actual event we are waiting will
500+
// be fired. The result could be a silent way to create
501+
// memory or file descriptor leaks, which is something
502+
// we should avoid.
503+
if (name !== 'error') {
504+
errorListener = (err) => {
505+
emitter.removeListener(name, eventListener);
506+
reject(err);
507+
};
508+
509+
emitter.once('error', errorListener);
510+
}
511+
512+
emitter.once(name, eventListener);
513+
});
514+
}

‎test/parallel/test-events-once.js

+93
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const { once, EventEmitter } = require('events');
5+
const { strictEqual, deepStrictEqual } = require('assert');
6+
7+
async function onceAnEvent() {
8+
const ee = new EventEmitter();
9+
10+
process.nextTick(() => {
11+
ee.emit('myevent', 42);
12+
});
13+
14+
const [value] = await once(ee, 'myevent');
15+
strictEqual(value, 42);
16+
strictEqual(ee.listenerCount('error'), 0);
17+
strictEqual(ee.listenerCount('myevent'), 0);
18+
}
19+
20+
async function onceAnEventWithTwoArgs() {
21+
const ee = new EventEmitter();
22+
23+
process.nextTick(() => {
24+
ee.emit('myevent', 42, 24);
25+
});
26+
27+
const value = await once(ee, 'myevent');
28+
deepStrictEqual(value, [42, 24]);
29+
}
30+
31+
async function catchesErrors() {
32+
const ee = new EventEmitter();
33+
34+
const expected = new Error('kaboom');
35+
let err;
36+
process.nextTick(() => {
37+
ee.emit('error', expected);
38+
});
39+
40+
try {
41+
await once(ee, 'myevent');
42+
} catch (_e) {
43+
err = _e;
44+
}
45+
strictEqual(err, expected);
46+
strictEqual(ee.listenerCount('error'), 0);
47+
strictEqual(ee.listenerCount('myevent'), 0);
48+
}
49+
50+
async function stopListeningAfterCatchingError() {
51+
const ee = new EventEmitter();
52+
53+
const expected = new Error('kaboom');
54+
let err;
55+
process.nextTick(() => {
56+
ee.emit('error', expected);
57+
ee.emit('myevent', 42, 24);
58+
});
59+
60+
process.on('multipleResolves', common.mustNotCall());
61+
62+
try {
63+
await once(ee, 'myevent');
64+
} catch (_e) {
65+
err = _e;
66+
}
67+
process.removeAllListeners('multipleResolves');
68+
strictEqual(err, expected);
69+
strictEqual(ee.listenerCount('error'), 0);
70+
strictEqual(ee.listenerCount('myevent'), 0);
71+
}
72+
73+
async function onceError() {
74+
const ee = new EventEmitter();
75+
76+
const expected = new Error('kaboom');
77+
process.nextTick(() => {
78+
ee.emit('error', expected);
79+
});
80+
81+
const [err] = await once(ee, 'error');
82+
strictEqual(err, expected);
83+
strictEqual(ee.listenerCount('error'), 0);
84+
strictEqual(ee.listenerCount('myevent'), 0);
85+
}
86+
87+
Promise.all([
88+
onceAnEvent(),
89+
onceAnEventWithTwoArgs(),
90+
catchesErrors(),
91+
stopListeningAfterCatchingError(),
92+
onceError()
93+
]);

0 commit comments

Comments
 (0)