• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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