Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions doc/api/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions lib/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 changes: 93 additions & 0 deletions test/parallel/test-events-once.js
Original file line number Diff line number Diff line change
@@ -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()
]);