As much as I would like to do this everywhere and then modify the lint rule to enforce it, the churn would be too big. However if we're going to have relatively frequent nits for this sort of thing (as we do), I'd prefer we migrate a few files at a time to never actually getting around to doing it. Ref: https://github.com/nodejs/node/pull/45448#pullrequestreview-1179370442 PR-URL: https://github.com/nodejs/node/pull/45466 Reviewed-By: James M Snell <jasnell@gmail.com>
320 lines
8 KiB
JavaScript
320 lines
8 KiB
JavaScript
'use strict';
|
|
const common = require('../common');
|
|
const assert = require('assert');
|
|
const { EventEmitter, captureRejectionSymbol } = require('events');
|
|
const { inherits } = require('util');
|
|
|
|
// Inherits from EE without a call to the
|
|
// parent constructor.
|
|
function NoConstructor() {
|
|
}
|
|
|
|
// captureRejections param validation
|
|
{
|
|
[1, [], function() {}, {}, Infinity, Math.PI, 'meow'].forEach((arg) => {
|
|
assert.throws(
|
|
() => new EventEmitter({ captureRejections: arg }),
|
|
{
|
|
name: 'TypeError',
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
message: 'The "options.captureRejections" property must be of type boolean.' +
|
|
common.invalidArgTypeHelper(arg),
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
inherits(NoConstructor, EventEmitter);
|
|
|
|
function captureRejections() {
|
|
const ee = new EventEmitter({ captureRejections: true });
|
|
const _err = new Error('kaboom');
|
|
ee.on('something', common.mustCall(async (value) => {
|
|
throw _err;
|
|
}));
|
|
|
|
ee.on('error', common.mustCall((err) => {
|
|
assert.strictEqual(err, _err);
|
|
process.nextTick(captureRejectionsTwoHandlers);
|
|
}));
|
|
|
|
ee.emit('something');
|
|
}
|
|
|
|
function captureRejectionsTwoHandlers() {
|
|
const ee = new EventEmitter({ captureRejections: true });
|
|
const _err = new Error('kaboom');
|
|
|
|
ee.on('something', common.mustCall(async (value) => {
|
|
throw _err;
|
|
}));
|
|
|
|
// throw twice
|
|
ee.on('something', common.mustCall(async (value) => {
|
|
throw _err;
|
|
}));
|
|
|
|
let count = 0;
|
|
|
|
ee.on('error', common.mustCall((err) => {
|
|
assert.strictEqual(err, _err);
|
|
if (++count === 2) {
|
|
process.nextTick(defaultValue);
|
|
}
|
|
}, 2));
|
|
|
|
ee.emit('something');
|
|
}
|
|
|
|
function defaultValue() {
|
|
const ee = new EventEmitter();
|
|
const _err = new Error('kaboom');
|
|
ee.on('something', common.mustCall(async (value) => {
|
|
throw _err;
|
|
}));
|
|
|
|
process.removeAllListeners('unhandledRejection');
|
|
|
|
process.once('unhandledRejection', common.mustCall((err) => {
|
|
// restore default
|
|
process.on('unhandledRejection', (err) => { throw err; });
|
|
|
|
assert.strictEqual(err, _err);
|
|
process.nextTick(globalSetting);
|
|
}));
|
|
|
|
ee.emit('something');
|
|
}
|
|
|
|
function globalSetting() {
|
|
assert.strictEqual(EventEmitter.captureRejections, false);
|
|
EventEmitter.captureRejections = true;
|
|
const ee = new EventEmitter();
|
|
const _err = new Error('kaboom');
|
|
ee.on('something', common.mustCall(async (value) => {
|
|
throw _err;
|
|
}));
|
|
|
|
ee.on('error', common.mustCall((err) => {
|
|
assert.strictEqual(err, _err);
|
|
|
|
// restore default
|
|
EventEmitter.captureRejections = false;
|
|
process.nextTick(configurable);
|
|
}));
|
|
|
|
ee.emit('something');
|
|
}
|
|
|
|
// We need to be able to configure this for streams, as we would
|
|
// like to call destro(err) there.
|
|
function configurable() {
|
|
const ee = new EventEmitter({ captureRejections: true });
|
|
const _err = new Error('kaboom');
|
|
ee.on('something', common.mustCall(async (...args) => {
|
|
assert.deepStrictEqual(args, [42, 'foobar']);
|
|
throw _err;
|
|
}));
|
|
|
|
assert.strictEqual(captureRejectionSymbol, Symbol.for('nodejs.rejection'));
|
|
|
|
ee[captureRejectionSymbol] = common.mustCall((err, type, ...args) => {
|
|
assert.strictEqual(err, _err);
|
|
assert.strictEqual(type, 'something');
|
|
assert.deepStrictEqual(args, [42, 'foobar']);
|
|
process.nextTick(globalSettingNoConstructor);
|
|
});
|
|
|
|
ee.emit('something', 42, 'foobar');
|
|
}
|
|
|
|
function globalSettingNoConstructor() {
|
|
assert.strictEqual(EventEmitter.captureRejections, false);
|
|
EventEmitter.captureRejections = true;
|
|
const ee = new NoConstructor();
|
|
const _err = new Error('kaboom');
|
|
ee.on('something', common.mustCall(async (value) => {
|
|
throw _err;
|
|
}));
|
|
|
|
ee.on('error', common.mustCall((err) => {
|
|
assert.strictEqual(err, _err);
|
|
|
|
// restore default
|
|
EventEmitter.captureRejections = false;
|
|
process.nextTick(thenable);
|
|
}));
|
|
|
|
ee.emit('something');
|
|
}
|
|
|
|
function thenable() {
|
|
const ee = new EventEmitter({ captureRejections: true });
|
|
const _err = new Error('kaboom');
|
|
ee.on('something', common.mustCall((value) => {
|
|
const obj = {};
|
|
|
|
Object.defineProperty(obj, 'then', {
|
|
get: common.mustCall(() => {
|
|
return common.mustCall((resolved, rejected) => {
|
|
assert.strictEqual(resolved, undefined);
|
|
rejected(_err);
|
|
});
|
|
}, 1), // Only 1 call for Promises/A+ compat.
|
|
});
|
|
|
|
return obj;
|
|
}));
|
|
|
|
ee.on('error', common.mustCall((err) => {
|
|
assert.strictEqual(err, _err);
|
|
process.nextTick(avoidLoopOnRejection);
|
|
}));
|
|
|
|
ee.emit('something');
|
|
}
|
|
|
|
function avoidLoopOnRejection() {
|
|
const ee = new EventEmitter({ captureRejections: true });
|
|
const _err1 = new Error('kaboom');
|
|
const _err2 = new Error('kaboom2');
|
|
ee.on('something', common.mustCall(async (value) => {
|
|
throw _err1;
|
|
}));
|
|
|
|
ee[captureRejectionSymbol] = common.mustCall(async (err) => {
|
|
assert.strictEqual(err, _err1);
|
|
throw _err2;
|
|
});
|
|
|
|
process.removeAllListeners('unhandledRejection');
|
|
|
|
process.once('unhandledRejection', common.mustCall((err) => {
|
|
// restore default
|
|
process.on('unhandledRejection', (err) => { throw err; });
|
|
|
|
assert.strictEqual(err, _err2);
|
|
process.nextTick(avoidLoopOnError);
|
|
}));
|
|
|
|
ee.emit('something');
|
|
}
|
|
|
|
function avoidLoopOnError() {
|
|
const ee = new EventEmitter({ captureRejections: true });
|
|
const _err1 = new Error('kaboom');
|
|
const _err2 = new Error('kaboom2');
|
|
ee.on('something', common.mustCall(async (value) => {
|
|
throw _err1;
|
|
}));
|
|
|
|
ee.on('error', common.mustCall(async (err) => {
|
|
assert.strictEqual(err, _err1);
|
|
throw _err2;
|
|
}));
|
|
|
|
process.removeAllListeners('unhandledRejection');
|
|
|
|
process.once('unhandledRejection', common.mustCall((err) => {
|
|
// restore default
|
|
process.on('unhandledRejection', (err) => { throw err; });
|
|
|
|
assert.strictEqual(err, _err2);
|
|
process.nextTick(thenableThatThrows);
|
|
}));
|
|
|
|
ee.emit('something');
|
|
}
|
|
|
|
function thenableThatThrows() {
|
|
const ee = new EventEmitter({ captureRejections: true });
|
|
const _err = new Error('kaboom');
|
|
ee.on('something', common.mustCall((value) => {
|
|
const obj = {};
|
|
|
|
Object.defineProperty(obj, 'then', {
|
|
get: common.mustCall(() => {
|
|
throw _err;
|
|
}, 1), // Only 1 call for Promises/A+ compat.
|
|
});
|
|
|
|
return obj;
|
|
}));
|
|
|
|
ee.on('error', common.mustCall((err) => {
|
|
assert.strictEqual(err, _err);
|
|
process.nextTick(resetCaptureOnThrowInError);
|
|
}));
|
|
|
|
ee.emit('something');
|
|
}
|
|
|
|
function resetCaptureOnThrowInError() {
|
|
const ee = new EventEmitter({ captureRejections: true });
|
|
ee.on('something', common.mustCall(async (value) => {
|
|
throw new Error('kaboom');
|
|
}));
|
|
|
|
ee.once('error', common.mustCall((err) => {
|
|
throw err;
|
|
}));
|
|
|
|
process.removeAllListeners('uncaughtException');
|
|
|
|
process.once('uncaughtException', common.mustCall((err) => {
|
|
process.nextTick(next);
|
|
}));
|
|
|
|
ee.emit('something');
|
|
|
|
function next() {
|
|
process.on('uncaughtException', common.mustNotCall());
|
|
|
|
const _err = new Error('kaboom2');
|
|
ee.on('something2', common.mustCall(async (value) => {
|
|
throw _err;
|
|
}));
|
|
|
|
ee.on('error', common.mustCall((err) => {
|
|
assert.strictEqual(err, _err);
|
|
|
|
process.removeAllListeners('uncaughtException');
|
|
|
|
// restore default
|
|
process.on('uncaughtException', (err) => { throw err; });
|
|
|
|
process.nextTick(argValidation);
|
|
}));
|
|
|
|
ee.emit('something2');
|
|
}
|
|
}
|
|
|
|
function argValidation() {
|
|
|
|
function testType(obj) {
|
|
const received = obj.constructor.name !== 'Number' ?
|
|
`an instance of ${obj.constructor.name}` :
|
|
`type number (${obj})`;
|
|
|
|
assert.throws(() => new EventEmitter({ captureRejections: obj }), {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
name: 'TypeError',
|
|
message: 'The "options.captureRejections" property must be of type ' +
|
|
`boolean. Received ${received}`,
|
|
});
|
|
|
|
assert.throws(() => EventEmitter.captureRejections = obj, {
|
|
code: 'ERR_INVALID_ARG_TYPE',
|
|
name: 'TypeError',
|
|
message: 'The "EventEmitter.captureRejections" property must be of ' +
|
|
`type boolean. Received ${received}`,
|
|
});
|
|
}
|
|
|
|
testType([]);
|
|
testType({ hello: 42 });
|
|
testType(42);
|
|
}
|
|
|
|
captureRejections();
|