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