1'use strict'; 2const common = require('../common'); 3const assert = require('assert'); 4const { EventEmitter, captureRejectionSymbol } = require('events'); 5const { inherits } = require('util'); 6 7// Inherits from EE without a call to the 8// parent constructor. 9function NoConstructor() { 10} 11 12// captureRejections param validation 13{ 14 [1, [], function() {}, {}, Infinity, Math.PI, 'meow'].forEach((arg) => { 15 assert.throws( 16 () => new EventEmitter({ captureRejections: arg }), 17 { 18 name: 'TypeError', 19 code: 'ERR_INVALID_ARG_TYPE', 20 message: 'The "options.captureRejections" property must be of type boolean.' + 21 common.invalidArgTypeHelper(arg), 22 } 23 ); 24 }); 25} 26 27inherits(NoConstructor, EventEmitter); 28 29function captureRejections() { 30 const ee = new EventEmitter({ captureRejections: true }); 31 const _err = new Error('kaboom'); 32 ee.on('something', common.mustCall(async (value) => { 33 throw _err; 34 })); 35 36 ee.on('error', common.mustCall((err) => { 37 assert.strictEqual(err, _err); 38 process.nextTick(captureRejectionsTwoHandlers); 39 })); 40 41 ee.emit('something'); 42} 43 44function captureRejectionsTwoHandlers() { 45 const ee = new EventEmitter({ captureRejections: true }); 46 const _err = new Error('kaboom'); 47 48 ee.on('something', common.mustCall(async (value) => { 49 throw _err; 50 })); 51 52 // throw twice 53 ee.on('something', common.mustCall(async (value) => { 54 throw _err; 55 })); 56 57 let count = 0; 58 59 ee.on('error', common.mustCall((err) => { 60 assert.strictEqual(err, _err); 61 if (++count === 2) { 62 process.nextTick(defaultValue); 63 } 64 }, 2)); 65 66 ee.emit('something'); 67} 68 69function defaultValue() { 70 const ee = new EventEmitter(); 71 const _err = new Error('kaboom'); 72 ee.on('something', common.mustCall(async (value) => { 73 throw _err; 74 })); 75 76 process.removeAllListeners('unhandledRejection'); 77 78 process.once('unhandledRejection', common.mustCall((err) => { 79 // restore default 80 process.on('unhandledRejection', (err) => { throw err; }); 81 82 assert.strictEqual(err, _err); 83 process.nextTick(globalSetting); 84 })); 85 86 ee.emit('something'); 87} 88 89function globalSetting() { 90 assert.strictEqual(EventEmitter.captureRejections, false); 91 EventEmitter.captureRejections = true; 92 const ee = new EventEmitter(); 93 const _err = new Error('kaboom'); 94 ee.on('something', common.mustCall(async (value) => { 95 throw _err; 96 })); 97 98 ee.on('error', common.mustCall((err) => { 99 assert.strictEqual(err, _err); 100 101 // restore default 102 EventEmitter.captureRejections = false; 103 process.nextTick(configurable); 104 })); 105 106 ee.emit('something'); 107} 108 109// We need to be able to configure this for streams, as we would 110// like to call destroy(err) there. 111function configurable() { 112 const ee = new EventEmitter({ captureRejections: true }); 113 const _err = new Error('kaboom'); 114 ee.on('something', common.mustCall(async (...args) => { 115 assert.deepStrictEqual(args, [42, 'foobar']); 116 throw _err; 117 })); 118 119 assert.strictEqual(captureRejectionSymbol, Symbol.for('nodejs.rejection')); 120 121 ee[captureRejectionSymbol] = common.mustCall((err, type, ...args) => { 122 assert.strictEqual(err, _err); 123 assert.strictEqual(type, 'something'); 124 assert.deepStrictEqual(args, [42, 'foobar']); 125 process.nextTick(globalSettingNoConstructor); 126 }); 127 128 ee.emit('something', 42, 'foobar'); 129} 130 131function globalSettingNoConstructor() { 132 assert.strictEqual(EventEmitter.captureRejections, false); 133 EventEmitter.captureRejections = true; 134 const ee = new NoConstructor(); 135 const _err = new Error('kaboom'); 136 ee.on('something', common.mustCall(async (value) => { 137 throw _err; 138 })); 139 140 ee.on('error', common.mustCall((err) => { 141 assert.strictEqual(err, _err); 142 143 // restore default 144 EventEmitter.captureRejections = false; 145 process.nextTick(thenable); 146 })); 147 148 ee.emit('something'); 149} 150 151function thenable() { 152 const ee = new EventEmitter({ captureRejections: true }); 153 const _err = new Error('kaboom'); 154 ee.on('something', common.mustCall((value) => { 155 const obj = {}; 156 157 Object.defineProperty(obj, 'then', { 158 get: common.mustCall(() => { 159 return common.mustCall((resolved, rejected) => { 160 assert.strictEqual(resolved, undefined); 161 rejected(_err); 162 }); 163 }, 1), // Only 1 call for Promises/A+ compat. 164 }); 165 166 return obj; 167 })); 168 169 ee.on('error', common.mustCall((err) => { 170 assert.strictEqual(err, _err); 171 process.nextTick(avoidLoopOnRejection); 172 })); 173 174 ee.emit('something'); 175} 176 177function avoidLoopOnRejection() { 178 const ee = new EventEmitter({ captureRejections: true }); 179 const _err1 = new Error('kaboom'); 180 const _err2 = new Error('kaboom2'); 181 ee.on('something', common.mustCall(async (value) => { 182 throw _err1; 183 })); 184 185 ee[captureRejectionSymbol] = common.mustCall(async (err) => { 186 assert.strictEqual(err, _err1); 187 throw _err2; 188 }); 189 190 process.removeAllListeners('unhandledRejection'); 191 192 process.once('unhandledRejection', common.mustCall((err) => { 193 // restore default 194 process.on('unhandledRejection', (err) => { throw err; }); 195 196 assert.strictEqual(err, _err2); 197 process.nextTick(avoidLoopOnError); 198 })); 199 200 ee.emit('something'); 201} 202 203function avoidLoopOnError() { 204 const ee = new EventEmitter({ captureRejections: true }); 205 const _err1 = new Error('kaboom'); 206 const _err2 = new Error('kaboom2'); 207 ee.on('something', common.mustCall(async (value) => { 208 throw _err1; 209 })); 210 211 ee.on('error', common.mustCall(async (err) => { 212 assert.strictEqual(err, _err1); 213 throw _err2; 214 })); 215 216 process.removeAllListeners('unhandledRejection'); 217 218 process.once('unhandledRejection', common.mustCall((err) => { 219 // restore default 220 process.on('unhandledRejection', (err) => { throw err; }); 221 222 assert.strictEqual(err, _err2); 223 process.nextTick(thenableThatThrows); 224 })); 225 226 ee.emit('something'); 227} 228 229function thenableThatThrows() { 230 const ee = new EventEmitter({ captureRejections: true }); 231 const _err = new Error('kaboom'); 232 ee.on('something', common.mustCall((value) => { 233 const obj = {}; 234 235 Object.defineProperty(obj, 'then', { 236 get: common.mustCall(() => { 237 throw _err; 238 }, 1), // Only 1 call for Promises/A+ compat. 239 }); 240 241 return obj; 242 })); 243 244 ee.on('error', common.mustCall((err) => { 245 assert.strictEqual(err, _err); 246 process.nextTick(resetCaptureOnThrowInError); 247 })); 248 249 ee.emit('something'); 250} 251 252function resetCaptureOnThrowInError() { 253 const ee = new EventEmitter({ captureRejections: true }); 254 ee.on('something', common.mustCall(async (value) => { 255 throw new Error('kaboom'); 256 })); 257 258 ee.once('error', common.mustCall((err) => { 259 throw err; 260 })); 261 262 process.removeAllListeners('uncaughtException'); 263 264 process.once('uncaughtException', common.mustCall((err) => { 265 process.nextTick(next); 266 })); 267 268 ee.emit('something'); 269 270 function next() { 271 process.on('uncaughtException', common.mustNotCall()); 272 273 const _err = new Error('kaboom2'); 274 ee.on('something2', common.mustCall(async (value) => { 275 throw _err; 276 })); 277 278 ee.on('error', common.mustCall((err) => { 279 assert.strictEqual(err, _err); 280 281 process.removeAllListeners('uncaughtException'); 282 283 // restore default 284 process.on('uncaughtException', (err) => { throw err; }); 285 286 process.nextTick(argValidation); 287 })); 288 289 ee.emit('something2'); 290 } 291} 292 293function argValidation() { 294 295 function testType(obj) { 296 const received = obj.constructor.name !== 'Number' ? 297 `an instance of ${obj.constructor.name}` : 298 `type number (${obj})`; 299 300 assert.throws(() => new EventEmitter({ captureRejections: obj }), { 301 code: 'ERR_INVALID_ARG_TYPE', 302 name: 'TypeError', 303 message: 'The "options.captureRejections" property must be of type ' + 304 `boolean. Received ${received}`, 305 }); 306 307 assert.throws(() => EventEmitter.captureRejections = obj, { 308 code: 'ERR_INVALID_ARG_TYPE', 309 name: 'TypeError', 310 message: 'The "EventEmitter.captureRejections" property must be of ' + 311 `type boolean. Received ${received}`, 312 }); 313 } 314 315 testType([]); 316 testType({ hello: 42 }); 317 testType(42); 318} 319 320captureRejections(); 321