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');
|
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
|
[`--trace-warnings`]: cli.html#cli_trace_warnings
|
||||||
[`EventEmitter.defaultMaxListeners`]: #events_eventemitter_defaultmaxlisteners
|
[`EventEmitter.defaultMaxListeners`]: #events_eventemitter_defaultmaxlisteners
|
||||||
[`domain`]: domain.html
|
[`domain`]: domain.html
|
||||||
|
@ -27,6 +27,7 @@ function EventEmitter() {
|
|||||||
EventEmitter.init.call(this);
|
EventEmitter.init.call(this);
|
||||||
}
|
}
|
||||||
module.exports = EventEmitter;
|
module.exports = EventEmitter;
|
||||||
|
module.exports.once = once;
|
||||||
|
|
||||||
// Backwards-compat with node 0.10.x
|
// Backwards-compat with node 0.10.x
|
||||||
EventEmitter.EventEmitter = EventEmitter;
|
EventEmitter.EventEmitter = EventEmitter;
|
||||||
@ -485,3 +486,32 @@ function unwrapListeners(arr) {
|
|||||||
}
|
}
|
||||||
return ret;
|
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