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