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