• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  Error,
5  ObjectDefineProperty,
6  WeakMap,
7} = primordials;
8
9const {
10  tickInfo,
11  promiseRejectEvents: {
12    kPromiseRejectWithNoHandler,
13    kPromiseHandlerAddedAfterReject,
14    kPromiseResolveAfterResolved,
15    kPromiseRejectAfterResolved
16  },
17  setPromiseRejectCallback
18} = internalBinding('task_queue');
19
20const {
21  noSideEffectsToString,
22  triggerUncaughtException
23} = internalBinding('errors');
24
25// *Must* match Environment::TickInfo::Fields in src/env.h.
26const kHasRejectionToWarn = 1;
27
28const maybeUnhandledPromises = new WeakMap();
29const pendingUnhandledRejections = [];
30const asyncHandledRejections = [];
31let lastPromiseId = 0;
32
33// --unhandled-rejections=none:
34// Emit 'unhandledRejection', but do not emit any warning.
35const kIgnoreUnhandledRejections = 0;
36
37// --unhandled-rejections=warn:
38// Emit 'unhandledRejection', then emit 'UnhandledPromiseRejectionWarning'.
39const kAlwaysWarnUnhandledRejections = 1;
40
41// --unhandled-rejections=strict:
42// Emit 'uncaughtException'. If it's not handled, print the error to stderr
43// and exit the process.
44// Otherwise, emit 'unhandledRejection'. If 'unhandledRejection' is not
45// handled, emit 'UnhandledPromiseRejectionWarning'.
46const kStrictUnhandledRejections = 2;
47
48// --unhandled-rejections=throw:
49// Emit 'unhandledRejection', if it's unhandled, emit
50// 'uncaughtException'. If it's not handled, print the error to stderr
51// and exit the process.
52const kThrowUnhandledRejections = 3;
53
54// --unhandled-rejections=warn-with-error-code:
55// Emit 'unhandledRejection', if it's unhandled, emit
56// 'UnhandledPromiseRejectionWarning', then set process exit code to 1.
57
58const kWarnWithErrorCodeUnhandledRejections = 4;
59
60// --unhandled-rejections is unset:
61// Emit 'unhandledRejection', if it's unhandled, emit
62// 'UnhandledPromiseRejectionWarning', then emit deprecation warning.
63const kDefaultUnhandledRejections = 5;
64
65let unhandledRejectionsMode;
66
67function setHasRejectionToWarn(value) {
68  tickInfo[kHasRejectionToWarn] = value ? 1 : 0;
69}
70
71function hasRejectionToWarn() {
72  return tickInfo[kHasRejectionToWarn] === 1;
73}
74
75function getUnhandledRejectionsMode() {
76  const { getOptionValue } = require('internal/options');
77  switch (getOptionValue('--unhandled-rejections')) {
78    case 'none':
79      return kIgnoreUnhandledRejections;
80    case 'warn':
81      return kAlwaysWarnUnhandledRejections;
82    case 'strict':
83      return kStrictUnhandledRejections;
84    case 'throw':
85      return kThrowUnhandledRejections;
86    case 'warn-with-error-code':
87      return kWarnWithErrorCodeUnhandledRejections;
88    default:
89      return kDefaultUnhandledRejections;
90  }
91}
92
93function promiseRejectHandler(type, promise, reason) {
94  if (unhandledRejectionsMode === undefined) {
95    unhandledRejectionsMode = getUnhandledRejectionsMode();
96  }
97  switch (type) {
98    case kPromiseRejectWithNoHandler:
99      unhandledRejection(promise, reason);
100      break;
101    case kPromiseHandlerAddedAfterReject:
102      handledRejection(promise);
103      break;
104    case kPromiseResolveAfterResolved:
105      resolveError('resolve', promise, reason);
106      break;
107    case kPromiseRejectAfterResolved:
108      resolveError('reject', promise, reason);
109      break;
110  }
111}
112
113function resolveError(type, promise, reason) {
114  // We have to wrap this in a next tick. Otherwise the error could be caught by
115  // the executed promise.
116  process.nextTick(() => {
117    process.emit('multipleResolves', type, promise, reason);
118  });
119}
120
121function unhandledRejection(promise, reason) {
122  maybeUnhandledPromises.set(promise, {
123    reason,
124    uid: ++lastPromiseId,
125    warned: false
126  });
127  // This causes the promise to be referenced at least for one tick.
128  pendingUnhandledRejections.push(promise);
129  setHasRejectionToWarn(true);
130}
131
132function handledRejection(promise) {
133  const promiseInfo = maybeUnhandledPromises.get(promise);
134  if (promiseInfo !== undefined) {
135    maybeUnhandledPromises.delete(promise);
136    if (promiseInfo.warned) {
137      const { uid } = promiseInfo;
138      // Generate the warning object early to get a good stack trace.
139      // eslint-disable-next-line no-restricted-syntax
140      const warning = new Error('Promise rejection was handled ' +
141                                `asynchronously (rejection id: ${uid})`);
142      warning.name = 'PromiseRejectionHandledWarning';
143      warning.id = uid;
144      asyncHandledRejections.push({ promise, warning });
145      setHasRejectionToWarn(true);
146      return;
147    }
148  }
149  if (maybeUnhandledPromises.size === 0 && asyncHandledRejections.length === 0)
150    setHasRejectionToWarn(false);
151}
152
153const unhandledRejectionErrName = 'UnhandledPromiseRejectionWarning';
154function emitUnhandledRejectionWarning(uid, reason) {
155  const warning = getErrorWithoutStack(
156    unhandledRejectionErrName,
157    'Unhandled promise rejection. This error originated either by ' +
158      'throwing inside of an async function without a catch block, ' +
159      'or by rejecting a promise which was not handled with .catch(). ' +
160      'To terminate the node process on unhandled promise ' +
161      'rejection, use the CLI flag `--unhandled-rejections=strict` (see ' +
162      'https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). ' +
163      `(rejection id: ${uid})`
164  );
165  try {
166    if (reason instanceof Error) {
167      warning.stack = reason.stack;
168      process.emitWarning(reason.stack, unhandledRejectionErrName);
169    } else {
170      process.emitWarning(
171        noSideEffectsToString(reason), unhandledRejectionErrName);
172    }
173  } catch {}
174
175  process.emitWarning(warning);
176}
177
178let deprecationWarned = false;
179function emitDeprecationWarning() {
180  process.emitWarning(
181    'Unhandled promise rejections are deprecated. In the future, ' +
182    'promise rejections that are not handled will terminate the ' +
183    'Node.js process with a non-zero exit code.',
184    'DeprecationWarning', 'DEP0018');
185}
186
187// If this method returns true, we've executed user code or triggered
188// a warning to be emitted which requires the microtask and next tick
189// queues to be drained again.
190function processPromiseRejections() {
191  let maybeScheduledTicksOrMicrotasks = asyncHandledRejections.length > 0;
192
193  while (asyncHandledRejections.length > 0) {
194    const { promise, warning } = asyncHandledRejections.shift();
195    if (!process.emit('rejectionHandled', promise)) {
196      process.emitWarning(warning);
197    }
198  }
199
200  let len = pendingUnhandledRejections.length;
201  while (len--) {
202    const promise = pendingUnhandledRejections.shift();
203    const promiseInfo = maybeUnhandledPromises.get(promise);
204    if (promiseInfo === undefined) {
205      continue;
206    }
207    promiseInfo.warned = true;
208    const { reason, uid } = promiseInfo;
209    switch (unhandledRejectionsMode) {
210      case kStrictUnhandledRejections: {
211        const err = reason instanceof Error ?
212          reason : generateUnhandledRejectionError(reason);
213        triggerUncaughtException(err, true /* fromPromise */);
214        const handled = process.emit('unhandledRejection', reason, promise);
215        if (!handled) emitUnhandledRejectionWarning(uid, reason);
216        break;
217      }
218      case kIgnoreUnhandledRejections: {
219        process.emit('unhandledRejection', reason, promise);
220        break;
221      }
222      case kAlwaysWarnUnhandledRejections: {
223        process.emit('unhandledRejection', reason, promise);
224        emitUnhandledRejectionWarning(uid, reason);
225        break;
226      }
227      case kThrowUnhandledRejections: {
228        const handled = process.emit('unhandledRejection', reason, promise);
229        if (!handled) {
230          const err = reason instanceof Error ?
231            reason : generateUnhandledRejectionError(reason);
232          triggerUncaughtException(err, true /* fromPromise */);
233        }
234        break;
235      }
236      case kWarnWithErrorCodeUnhandledRejections: {
237        const handled = process.emit('unhandledRejection', reason, promise);
238        if (!handled) {
239          emitUnhandledRejectionWarning(uid, reason);
240          process.exitCode = 1;
241        }
242        break;
243      }
244      case kDefaultUnhandledRejections: {
245        const handled = process.emit('unhandledRejection', reason, promise);
246        if (!handled) {
247          emitUnhandledRejectionWarning(uid, reason);
248          if (!deprecationWarned) {
249            emitDeprecationWarning();
250            deprecationWarned = true;
251          }
252        }
253        break;
254      }
255    }
256    maybeScheduledTicksOrMicrotasks = true;
257  }
258  return maybeScheduledTicksOrMicrotasks ||
259         pendingUnhandledRejections.length !== 0;
260}
261
262function getErrorWithoutStack(name, message) {
263  // Reset the stack to prevent any overhead.
264  const tmp = Error.stackTraceLimit;
265  Error.stackTraceLimit = 0;
266  // eslint-disable-next-line no-restricted-syntax
267  const err = new Error(message);
268  Error.stackTraceLimit = tmp;
269  ObjectDefineProperty(err, 'name', {
270    value: name,
271    enumerable: false,
272    writable: true,
273    configurable: true,
274  });
275  return err;
276}
277
278function generateUnhandledRejectionError(reason) {
279  const message =
280    'This error originated either by ' +
281    'throwing inside of an async function without a catch block, ' +
282    'or by rejecting a promise which was not handled with .catch().' +
283    ' The promise rejected with the reason ' +
284    `"${noSideEffectsToString(reason)}".`;
285
286  const err = getErrorWithoutStack('UnhandledPromiseRejection', message);
287  err.code = 'ERR_UNHANDLED_REJECTION';
288  return err;
289}
290
291function listenForRejections() {
292  setPromiseRejectCallback(promiseRejectHandler);
293}
294
295module.exports = {
296  hasRejectionToWarn,
297  setHasRejectionToWarn,
298  listenForRejections,
299  processPromiseRejections
300};
301