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