Skip to content

Commit

Permalink
Merge pull request #70 from Gozala/events.once
Browse files Browse the repository at this point in the history
Add `events.once`
  • Loading branch information
goto-bus-stop committed Jul 22, 2020
2 parents 2686023 + e55646e commit 2789456
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 0 deletions.
30 changes: 30 additions & 0 deletions events.js
Expand Up @@ -54,6 +54,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 @@ -444,3 +445,32 @@ function unwrapListeners(arr) {
}
return ret;
}

function once(emitter, name) {
return new Promise(function (resolve, reject) {
function eventListener() {
if (errorListener !== undefined) {
emitter.removeListener('error', errorListener);
}
resolve([].slice.call(arguments));
};
var 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 = function errorListener(err) {
emitter.removeListener(name, eventListener);
reject(err);
};

emitter.once('error', errorListener);
}

emitter.once(name, eventListener);
});
}
100 changes: 100 additions & 0 deletions tests/events-once.js
@@ -0,0 +1,100 @@
'use strict';

var common = require('./common');
var EventEmitter = require('../').EventEmitter;
var once = require('../').once;
var assert = require('assert');

function onceAnEvent() {
var ee = new EventEmitter();

process.nextTick(function () {
ee.emit('myevent', 42);
});

return once(ee, 'myevent').then(function (args) {
var value = args[0]
assert.strictEqual(value, 42);
assert.strictEqual(ee.listenerCount('error'), 0);
assert.strictEqual(ee.listenerCount('myevent'), 0);
});
}

function onceAnEventWithTwoArgs() {
var ee = new EventEmitter();

process.nextTick(function () {
ee.emit('myevent', 42, 24);
});

return once(ee, 'myevent').then(function (value) {
assert.strictEqual(value.length, 2);
assert.strictEqual(value[0], 42);
assert.strictEqual(value[1], 24);
});
}

function catchesErrors() {
var ee = new EventEmitter();

var expected = new Error('kaboom');
var err;
process.nextTick(function () {
ee.emit('error', expected);
});

return once(ee, 'myevent').then(function () {
throw new Error('should reject')
}, function (err) {
assert.strictEqual(err, expected);
assert.strictEqual(ee.listenerCount('error'), 0);
assert.strictEqual(ee.listenerCount('myevent'), 0);
});
}

function stopListeningAfterCatchingError() {
var ee = new EventEmitter();

var expected = new Error('kaboom');
var err;
process.nextTick(function () {
ee.emit('error', expected);
ee.emit('myevent', 42, 24);
});

// process.on('multipleResolves', common.mustNotCall());

return once(ee, 'myevent').then(common.mustNotCall, function (err) {
// process.removeAllListeners('multipleResolves');
assert.strictEqual(err, expected);
assert.strictEqual(ee.listenerCount('error'), 0);
assert.strictEqual(ee.listenerCount('myevent'), 0);
});
}

function onceError() {
var ee = new EventEmitter();

var expected = new Error('kaboom');
process.nextTick(function () {
ee.emit('error', expected);
});

return once(ee, 'error').then(function (args) {
var err = args[0]
assert.strictEqual(err, expected);
assert.strictEqual(ee.listenerCount('error'), 0);
assert.strictEqual(ee.listenerCount('myevent'), 0);
});
}

Promise.all([
onceAnEvent(),
onceAnEventWithTwoArgs(),
catchesErrors(),
stopListeningAfterCatchingError(),
onceError()
]).catch(function (err) {
console.error(err.stack)
process.exit(1)
});
6 changes: 6 additions & 0 deletions tests/index.js
Expand Up @@ -23,6 +23,12 @@ require('./add-listeners.js');
require('./check-listener-leaks.js');
require('./errors.js');
require('./events-list.js');
if (typeof Promise === 'function') {
require('./events-once.js');
} else {
// Promise support is not available.
test('./events-once.js', { skip: true }, function () {});
}
require('./listener-count.js');
require('./listeners-side-effects.js');
require('./listeners.js');
Expand Down

0 comments on commit 2789456

Please sign in to comment.