| '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 destroy(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(); |