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: https://github.com/nodejs/node/pull/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>
This commit is contained in:
parent
2a5edafabd
commit
b1ef279d57
@ -653,6 +653,47 @@ newListeners[0]();
|
||||
emitter.emit('log');
|
||||
```
|
||||
|
||||
## events.once(emitter, name)
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
* `emitter` {EventEmitter}
|
||||
* `name` {string}
|
||||
* Returns: {Promise}
|
||||
|
||||
Creates a `Promise` that is resolved when the `EventEmitter` emits the given
|
||||
event or that is rejected when the `EventEmitter` emits `'error'`.
|
||||
The `Promise` will resolve with an array of all the arguments emitted to the
|
||||
given event.
|
||||
|
||||
```js
|
||||
const { once, EventEmitter } = require('events');
|
||||
|
||||
async function run() {
|
||||
const ee = new EventEmitter();
|
||||
|
||||
process.nextTick(() => {
|
||||
ee.emit('myevent', 42);
|
||||
});
|
||||
|
||||
const [value] = await once(ee, 'myevent');
|
||||
console.log(value);
|
||||
|
||||
const err = new Error('kaboom');
|
||||
process.nextTick(() => {
|
||||
ee.emit('error', err);
|
||||
});
|
||||
|
||||
try {
|
||||
await once(ee, 'myevent');
|
||||
} catch (err) {
|
||||
console.log('error happened', err);
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
```
|
||||
|
||||
[`--trace-warnings`]: cli.html#cli_trace_warnings
|
||||
[`EventEmitter.defaultMaxListeners`]: #events_eventemitter_defaultmaxlisteners
|
||||
[`domain`]: domain.html
|
||||
|
@ -27,6 +27,7 @@ function EventEmitter() {
|
||||
EventEmitter.init.call(this);
|
||||
}
|
||||
module.exports = EventEmitter;
|
||||
module.exports.once = once;
|
||||
|
||||
// Backwards-compat with node 0.10.x
|
||||
EventEmitter.EventEmitter = EventEmitter;
|
||||
@ -485,3 +486,32 @@ function unwrapListeners(arr) {
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function once(emitter, name) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const eventListener = (...args) => {
|
||||
if (errorListener !== undefined) {
|
||||
emitter.removeListener('error', errorListener);
|
||||
}
|
||||
resolve(args);
|
||||
};
|
||||
let errorListener;
|
||||
|
||||
// Adding an error listener is not optional because
|
||||
// if an error is thrown on an event emitter we cannot
|
||||
// guarantee that the actual event we are waiting will
|
||||
// be fired. The result could be a silent way to create
|
||||
// memory or file descriptor leaks, which is something
|
||||
// we should avoid.
|
||||
if (name !== 'error') {
|
||||
errorListener = (err) => {
|
||||
emitter.removeListener(name, eventListener);
|
||||
reject(err);
|
||||
};
|
||||
|
||||
emitter.once('error', errorListener);
|
||||
}
|
||||
|
||||
emitter.once(name, eventListener);
|
||||
});
|
||||
}
|
||||
|
93
test/parallel/test-events-once.js
Normal file
93
test/parallel/test-events-once.js
Normal file
@ -0,0 +1,93 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const { once, EventEmitter } = require('events');
|
||||
const { strictEqual, deepStrictEqual } = require('assert');
|
||||
|
||||
async function onceAnEvent() {
|
||||
const ee = new EventEmitter();
|
||||
|
||||
process.nextTick(() => {
|
||||
ee.emit('myevent', 42);
|
||||
});
|
||||
|
||||
const [value] = await once(ee, 'myevent');
|
||||
strictEqual(value, 42);
|
||||
strictEqual(ee.listenerCount('error'), 0);
|
||||
strictEqual(ee.listenerCount('myevent'), 0);
|
||||
}
|
||||
|
||||
async function onceAnEventWithTwoArgs() {
|
||||
const ee = new EventEmitter();
|
||||
|
||||
process.nextTick(() => {
|
||||
ee.emit('myevent', 42, 24);
|
||||
});
|
||||
|
||||
const value = await once(ee, 'myevent');
|
||||
deepStrictEqual(value, [42, 24]);
|
||||
}
|
||||
|
||||
async function catchesErrors() {
|
||||
const ee = new EventEmitter();
|
||||
|
||||
const expected = new Error('kaboom');
|
||||
let err;
|
||||
process.nextTick(() => {
|
||||
ee.emit('error', expected);
|
||||
});
|
||||
|
||||
try {
|
||||
await once(ee, 'myevent');
|
||||
} catch (_e) {
|
||||
err = _e;
|
||||
}
|
||||
strictEqual(err, expected);
|
||||
strictEqual(ee.listenerCount('error'), 0);
|
||||
strictEqual(ee.listenerCount('myevent'), 0);
|
||||
}
|
||||
|
||||
async function stopListeningAfterCatchingError() {
|
||||
const ee = new EventEmitter();
|
||||
|
||||
const expected = new Error('kaboom');
|
||||
let err;
|
||||
process.nextTick(() => {
|
||||
ee.emit('error', expected);
|
||||
ee.emit('myevent', 42, 24);
|
||||
});
|
||||
|
||||
process.on('multipleResolves', common.mustNotCall());
|
||||
|
||||
try {
|
||||
await once(ee, 'myevent');
|
||||
} catch (_e) {
|
||||
err = _e;
|
||||
}
|
||||
process.removeAllListeners('multipleResolves');
|
||||
strictEqual(err, expected);
|
||||
strictEqual(ee.listenerCount('error'), 0);
|
||||
strictEqual(ee.listenerCount('myevent'), 0);
|
||||
}
|
||||
|
||||
async function onceError() {
|
||||
const ee = new EventEmitter();
|
||||
|
||||
const expected = new Error('kaboom');
|
||||
process.nextTick(() => {
|
||||
ee.emit('error', expected);
|
||||
});
|
||||
|
||||
const [err] = await once(ee, 'error');
|
||||
strictEqual(err, expected);
|
||||
strictEqual(ee.listenerCount('error'), 0);
|
||||
strictEqual(ee.listenerCount('myevent'), 0);
|
||||
}
|
||||
|
||||
Promise.all([
|
||||
onceAnEvent(),
|
||||
onceAnEventWithTwoArgs(),
|
||||
catchesErrors(),
|
||||
stopListeningAfterCatchingError(),
|
||||
onceError()
|
||||
]);
|
Loading…
x
Reference in New Issue
Block a user