preface
As [LIbuv source study notes] 1. Event cycle supplement, this main point of knowledge involved
- Running microtasks in Node
- Implementation of process. NextTick in node
- The specific implementation of setTimeout, setInterval, and setImmediate in Node
- Implementation of process unhandledRejection, uncaughtException, Warning event listener in node
- Async_hooks in node
- v8 GetMicrotaskQueue()->PerformCheckpoint api
- v8 SetPromiseRejectCallback api
- v8 AddMessageListenerWithErrorLevel api
- v8 isEmpty api
example
This is a self-written example, mainly used to understand the order of execution from the code implementation.
// time.js process.on("unhandledRejection", (err) => { console.log(1); }); console.log(2); setTimeout(() => { console.log(3); Promise. Resolve (). Then (= > console (_). The log (3.1)); Process. NextTick (() = > {the console. The log (3.2); }); Promise. Resolve (). Then (= > console (_). The log (3.3)); The console. The log (3.4); }, 0); Promise.reject().finally((_) => console.log(4)); setImmediate(() => { console.log(5) }) process.nextTick(() => { console.log(6); });Copy the code
The code runs as follows:
➜ test node time.js
2
6
4
1
3
3.4
3.2
3.1
3.3
5
Copy the code
Example run analysis
- Console. log(1) – unhandledRejection event is listened for by the event mechanism. It will not run until the event is triggered
- Console.log (2) – Run directly
- Console. log(3.x) – Registers an event loop phase a Timer phase callback function. It will not run until the event loop runs until this phase is triggered.
- Console.log (4) – Generates a promise. reject event and registers a V8 microtask.
- Console. log(5) – Registers a callback function for the phase 6 Check of the event loop, which will not run until the event loop is fired.
- Console.log (6) – Registers a callback via nodeJS ‘nextTick function.
Tasks in the browser
In the browser’s JS event loop, setTimeout is usually referred to as a macro task, promise. resolve and so on are referred to as microtasks, that is, setTimeout creates a macro task that executes the main function code first, and then executes the microtask code. If there are other macro tasks, they will be run in the next event loop.
Tasks in Node
In fact, Node also follows the standard set of macro and micro tasks implemented in browsers. Let’s look at the implementation of setTimeout in node, which also involves the implementation of nextTick. This explains why console.log(6) appears before console.log(4).
setTimeout
Basically, we instantiate a Timeout object and insert it into a queue by calling the INSERT method.
// lib/timers.js function setTimeout(callback, after, arg1, arg2, arg3) { validateCallback(callback); let i, args; switch (arguments.length) { // fast cases case 1: case 2: break; case 3: args = [arg1]; break; case 4: args = [arg1, arg2]; break; default: args = [arg1, arg2, arg3]; for (i = 5; i < arguments.length; I ++) {// Extend array dynamically, makes. Apply run much faster in v6.0.0args [i-2] = arguments[I]; } break; } const timeout = new Timeout(callback, after, args, false, true); insert(timeout, timeout._idleTimeout); return timeout; }Copy the code
insert
The timerListMap object is mounted to a global timerListMap object with a timeout of 1000ms.
The callback function has been registered through the above steps, after reading [libuv source study notes] 1. Event loop We know that setTimeout is called in the phase of the event loop, the Timer phase.
How do you register for the libuv event loop?
function insert(item, msecs, start = getLibuvNow()) {
// Truncate so that accuracy of sub-millisecond timers is not assumed.
msecs = MathTrunc(msecs);
item._idleStart = start;
// Use an existing list if there is one, otherwise we need to make a new one.
let list = timerListMap[msecs];
if (list === undefined) {
debug('no %d list was found in insert, creating a new one', msecs);
const expiry = start + msecs;
timerListMap[msecs] = list = new TimersList(expiry, msecs);
timerListQueue.insert(list);
if (nextExpiry > expiry) {
scheduleTimer(msecs);
nextExpiry = expiry;
}
}
L.append(list, item);
}
Copy the code
setupTimers
SetupTimers are the implementation of registering with the LibuV event loop Timer phase.
- The setupTimers function is called, passing two function parameters.
ProcessTimers runs the callback set by setTimeout, and processImmediate runs the callback set by setImmediate, so they register with Libuv in the same way.
// lib/internal/bootstrap/node.js const { setupTimers } = internalBinding('timers'); const { getTimerCallbacks } = require('internal/timers'); const { setupTimers } = internalBinding('timers'); const { processImmediate, processTimers } = getTimerCallbacks(runNextTicks); // Sets two per-Environment callbacks that will be run from libuv: // - processImmediate will be run in the callback of the per-Environment // check handle. // - processTimers will be run in the callback of the per-Environment timer. setupTimers(processImmediate, processTimers);Copy the code
- SetupTimers are defined from SRC /timers.cc. You can see that the set_immediate_callback_function and set_timers_callback_function functions are mainly called.
// src/timers.cc void Initialize(Local<Object> target, Local<Value> unused, Local<Context> context, void* priv) { Environment* env = Environment::GetCurrent(context); . env->SetMethod(target, "setupTimers", SetupTimers); . } void SetupTimers(const FunctionCallbackInfo<Value>& args) { CHECK(args[0]->IsFunction()); CHECK(args[1]->IsFunction()); auto env = Environment::GetCurrent(args); env->set_immediate_callback_function(args[0].As<Function>()); env->set_timers_callback_function(args[1].As<Function>()); }Copy the code
- Timers_callback_function When is it called after setting?
You can see that in the RunTimers function, the timers_callback_function is called and there is a do while loop. The timers_callback_function is run until the ret.isempty () && env->can_call_into_js() value is true.
As for when IsEmpty returns true, let’s look at v8’s documentation, which simply means that a value greater than 0 is false, -1 is true, otherwise timers_callback_function is called all the time.
IsEmpty(expression)
Returns -1 (TRUE) if a variant has been initialized; 0 (FALSE) otherwise.
Copy the code
// src/env.cc void Environment::RunTimers(uv_timer_t* handle) { ... Local<Function> cb = env->timers_callback_function(); do { TryCatchScope try_catch(env); try_catch.SetVerbose(true); ret = cb->Call(env->context(), process, 1, &arg); // https://docs.oracle.com/cd/B40099_02/books/VBLANG/VBLANGVBLangRef136.html } while (ret.IsEmpty() && env->can_call_into_js()); . int64_t expiry_ms = ret.ToLocalChecked()->IntegerValue(env->context()).FromJust(); uv_handle_t* h = reinterpret_cast<uv_handle_t*>(handle); if (expiry_ms ! = 0) { int64_t duration_ms = llabs(expiry_ms) - (uv_now(env->event_loop()) - env->timer_base()); env->ScheduleTimer(duration_ms > 0 ? duration_ms : 1); . }Copy the code
Timers_callback_function is the processTimers function passed in by the setupTimers function above, and we should take a look at its implementation first.
- processTimers
If the TimersList is not time enough to call, the TimersList is returned. The RunTimers IsEmpty function returns false to break out of the do while loop, and the ScheduleTimer function is called.
// lib/internal/timers.js
function processTimers(now) {
debug('process timer lists %d', now);
nextExpiry = Infinity;
let list;
let ranAtLeastOneList = false;
while (list = timerListQueue.peek()) {
if (list.expiry > now) {
nextExpiry = list.expiry;
return refCount > 0 ? nextExpiry : -nextExpiry;
}
if (ranAtLeastOneList)
runNextTicks();
else
ranAtLeastOneList = true;
listOnTimeout(list, now);
}
return 0;
}
Copy the code
- ScheduleTimer
This is where you get on the libuv event loop and register for the Timer phase. This phase essentially calls processTimers after duration_ms has been called.
void Environment::ScheduleTimer(int64_t duration_ms) {
if (started_cleanup_) return;
uv_timer_start(timer_handle(), RunTimers, duration_ms, 0);
}
Copy the code
listOnTimeout
The processTimers function mainly calls the listOnTimeout function. For example, the 1000ms set by the list has been timed out by the event loop
- Insert (Timeout); insert (Timeout)
- Run the timeout._ontimeout function, the callback passed by setTimeout
- If the _repeat property of Timeout is found to be true, the insert function will continue to be inserted into the list, which is the implementation of setInterval.
- When the second Timeout object is taken, and the ranAtLeastOneTimer is true, the runNextTicks function is called, and the microtask is run after the macro task has been run.
function listOnTimeout(list, now) { const msecs = list.msecs; debug('timeout callback %d', msecs); let ranAtLeastOneTimer = false; let timer; while (timer = L.peek(list)) { const diff = now - timer._idleStart; // Check if this loop iteration is too early for the next timer. // This happens if there are more timers scheduled for later in the list. if (diff < msecs) { list.expiry = MathMax(timer._idleStart + msecs, now + 1); list.id = timerListId++; timerListQueue.percolateDown(1); debug('%d list wait because diff is %d', msecs, diff); return; } if (ranAtLeastOneTimer) runNextTicks(); else ranAtLeastOneTimer = true; // The actual logic for when a timeout happens. L.remove(timer); const asyncId = timer[async_id_symbol]; if (! timer._onTimeout) { if (! timer._destroyed) { timer._destroyed = true; if (timer[kRefed]) refCount--; if (destroyHooksExist()) emitDestroy(asyncId); } continue; } emitBefore(asyncId, timer[trigger_async_id_symbol], timer); let start; if (timer._repeat) start = getLibuvNow(); try { const args = timer._timerArgs; if (args === undefined) timer._onTimeout(); else ReflectApply(timer._onTimeout, timer, args); } finally { if (timer._repeat && timer._idleTimeout ! == -1) { timer._idleTimeout = timer._repeat; insert(timer, timer._idleTimeout, start); } else if (! timer._idleNext && ! timer._idlePrev && ! timer._destroyed) { timer._destroyed = true; if (timer[kRefed]) refCount--; if (destroyHooksExist()) emitDestroy(asyncId); } } emitAfter(asyncId); } // If `L.peek(list)` returned nothing, the list was either empty or we have // called all of the timer timeouts. // As such, we can remove the list from the object map and // the PriorityQueue. debug('%d list empty', msecs); // The current list may have been removed and recreated since the reference // to `list` was created. Make sure they're the same instance of the list // before destroying. if (list === timerListMap[msecs]) { delete timerListMap[msecs]; timerListQueue.shift(); }}Copy the code
runNextTicks
As you can see from the above, after running a setTimeout callback, the node microtask will be started by calling the runNextTicks function, which implements the standard macro task and microtask of the browser.
If no nextTick or promiseRejection only run runMicrotasks function, otherwise run processTicksAndRejections function.
function runNextTicks() { if (! hasTickScheduled() && ! hasRejectionToWarn()) runMicrotasks(); if (! hasTickScheduled() && ! hasRejectionToWarn()) return; processTicksAndRejections(); }Copy the code
processTicksAndRejections
- First run the callback function for nextTick
- Then run runMicrotasks and see that this also proves that nextTick was executed before v8’s microtasks
- Since this is a do while loop, execute the content in the DO first, and run the processPromiseRejections function in the while
function processTicksAndRejections() { let tock; do { while (tock = queue.shift()) { const asyncId = tock[async_id_symbol]; emitBefore(asyncId, tock[trigger_async_id_symbol], tock); try { const callback = tock.callback; if (tock.args === undefined) { callback(); } else { const args = tock.args; switch (args.length) { case 1: callback(args[0]); break; case 2: callback(args[0], args[1]); break; case 3: callback(args[0], args[1], args[2]); break; case 4: callback(args[0], args[1], args[2], args[3]); break; default: callback(... args); } } } finally { if (destroyHooksExist()) emitDestroy(asyncId); } emitAfter(asyncId); } runMicrotasks(); } while (! queue.isEmpty() || processPromiseRejections()); setHasTickScheduled(false); setHasRejectionToWarn(false); }Copy the code
Maybe nextTick implementation, is to push a tickObject in the queue, in the above processTicksAndRejections function is removed from the queue to run again.
function nextTick(callback) {
validateCallback(callback);
if (process._exiting)
return;
let args;
switch (arguments.length) {
case 1: break;
case 2: args = [arguments[1]]; break;
case 3: args = [arguments[1], arguments[2]]; break;
case 4: args = [arguments[1], arguments[2], arguments[3]]; break;
default:
args = new Array(arguments.length - 1);
for (let i = 1; i < arguments.length; i++)
args[i - 1] = arguments[i];
}
if (queue.isEmpty())
setHasTickScheduled(true);
const asyncId = newAsyncId();
const triggerAsyncId = getDefaultTriggerAsyncId();
const tickObject = {
[async_id_symbol]: asyncId,
[trigger_async_id_symbol]: triggerAsyncId,
callback,
args
};
if (initHooksExist())
emitInit(asyncId, 'TickObject', triggerAsyncId, tickObject);
queue.push(tickObject);
}
Copy the code
As you may have noticed, all asynchronous operations in Node call emitInit, emitBefore, emitDestroy, emitAfter, etc. This is also how async_hooks are implemented.
runMicrotasks
Microtaskqueue ()->PerformCheckpoint API
static void Initialize(Local<Object> target, Local<Value> unused, Local<Context> context, void* priv) { Environment* env = Environment::GetCurrent(context); Isolate* isolate = env->isolate(); . env->SetMethod(target, "runMicrotasks", RunMicrotasks); . } static void RunMicrotasks(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); env->context()->GetMicrotaskQueue()->PerformCheckpoint(env->isolate()); }Copy the code
processPromiseRejections
In a Node microtask
- Run the callback for nextTick
- Run runMicrotasks, v8’s microtask queue
- Run the processPromiseRejections function
Found at the end of a micro task will run processPromiseRejections function, bring out all the callback function in the pendingUnhandledRejections to run once, The information for each promise is from the maybeUnhandledPromises map.
The error handling is also determined based on the –unhandled- Rejection parameter of Node A.js.
function processPromiseRejections() { let maybeScheduledTicksOrMicrotasks = asyncHandledRejections.length > 0; while (asyncHandledRejections.length > 0) { const { promise, warning } = ArrayPrototypeShift(asyncHandledRejections); if (! process.emit('rejectionHandled', promise)) { process.emitWarning(warning); } } let len = pendingUnhandledRejections.length; while (len--) { const promise = ArrayPrototypeShift(pendingUnhandledRejections); const promiseInfo = maybeUnhandledPromises.get(promise); if (promiseInfo === undefined) { continue; } promiseInfo.warned = true; const { reason, uid, emit } = promiseInfo; switch (unhandledRejectionsMode) { case kStrictUnhandledRejections: { const err = reason instanceof Error ? reason : generateUnhandledRejectionError(reason); triggerUncaughtException(err, true /* fromPromise */); const handled = emit(reason, promise, promiseInfo); if (! handled) emitUnhandledRejectionWarning(uid, reason); break; } case kIgnoreUnhandledRejections: { emit(reason, promise, promiseInfo); break; } case kAlwaysWarnUnhandledRejections: { emit(reason, promise, promiseInfo); emitUnhandledRejectionWarning(uid, reason); break; } case kThrowUnhandledRejections: { const handled = emit(reason, promise, promiseInfo); if (! handled) { const err = reason instanceof Error ? reason : generateUnhandledRejectionError(reason); triggerUncaughtException(err, true /* fromPromise */); } break; } case kWarnWithErrorCodeUnhandledRejections: { const handled = emit(reason, promise, promiseInfo); if (! handled) { emitUnhandledRejectionWarning(uid, reason); process.exitCode = 1; } break; } } maybeScheduledTicksOrMicrotasks = true; } return maybeScheduledTicksOrMicrotasks || pendingUnhandledRejections.length ! = = 0; }Copy the code
When was the then pendingUnhandledRejections push data?
setPromiseRejectCallback
Turned out to be in the lib/internal/process/promises. Js first through the v8 SetPromiseRejectCallback API set all the rejectCallback Promise
// lib/internal/process/promises.js
function listenForRejections() {
setPromiseRejectCallback(promiseRejectHandler);
}
Copy the code
// src/env.cc static void Initialize(Local<Object> target, Local<Value> unused, Local<Context> context, void* priv) { Environment* env = Environment::GetCurrent(context); . env->SetMethod(target, "setPromiseRejectCallback", SetPromiseRejectCallback); }Copy the code
promiseRejectHandler
When a promise is rejected and the following conditions occur, the promiser Ejthandler function is triggered.
// src/env.cc function promiseRejectHandler(type, promise, reason) { if (unhandledRejectionsMode === undefined) { unhandledRejectionsMode = getUnhandledRejectionsMode(); } switch (type) { case kPromiseRejectWithNoHandler: unhandledRejection(promise, reason); break; case kPromiseHandlerAddedAfterReject: handledRejection(promise); break; case kPromiseResolveAfterResolved: resolveError('resolve', promise, reason); break; case kPromiseRejectAfterResolved: resolveError('reject', promise, reason); break; }}Copy the code
// deps/v8/include/v8.h
enum PromiseRejectEvent {
kPromiseRejectWithNoHandler = 0,
kPromiseHandlerAddedAfterReject = 1,
kPromiseRejectAfterResolved = 2,
kPromiseResolveAfterResolved = 3,
};
Copy the code
- If the condition 1 kPromiseRejectWithNoHandler established call unhandledRejection function, Mainly in maybeUnhandledPromises there are properties that are mounted for keys. And push into pendingUnhandledRejections data, its execution time is what is said above processPromiseRejections function inside, which is a micro task finally will perform the function defined in the emit method.
Emit an event by calling the process.emit method. If the process is listening for an unhandledRejection event, then the user’s callback console.log(1); Is triggered.
// lib/internal/process/promises.js function unhandledRejection(promise, reason) { const asyncId = async_hooks.executionAsyncId(); const triggerAsyncId = async_hooks.triggerAsyncId(); const resource = promise; const emit = (reason, promise, promiseInfo) => { try { pushAsyncContext(asyncId, triggerAsyncId, resource); if (promiseInfo.domain) { return promiseInfo.domain.emit('error', reason); } return process.emit('unhandledRejection', reason, promise); } finally { popAsyncContext(asyncId); }}; maybeUnhandledPromises.set(promise, { reason, uid: ++lastPromiseId, warned: false, domain: process.domain, emit }); // This causes the promise to be referenced at least for one tick. ArrayPrototypePush(pendingUnhandledRejections, promise); setHasRejectionToWarn(true); }Copy the code
- If the conditions established 2 kPromiseHandlerAddedAfterReject handledRejection function called, by analyzing AddedAfterReject name should be in Promise Reject once, Added condition for RejectHandler. Similar to the example I write below, the second promise.reject does not cause the unhandledRejection event of the process to fire. The handledRejection function is an example of how this function will not be triggered the second time.
var p = Promise.reject().finally(() => console.log(7)); P.t hen (() = > Promise. Reject ()). The catch (() = > {the console. The log (7.1); });Copy the code
As you can see, in order not to trigger the unhandledRejection event a second time, the promise will be removed from the maybeUnhandledPromises map. Because the processPromiseRejections function will skip this while loop, but will push asyncHandledRejections as promised when true. Promiseinfo. warned = true is set in processPromiseRejections, every time a Promise is rejected and an error handle is attached to it (for example, The ‘rejectionHandled’ event is emitted when you use promising ()) after a Node.js event loop. The implementation also validates the definition of the ‘rejectionHandled’ event document.
function handledRejection(promise) { const promiseInfo = maybeUnhandledPromises.get(promise); if (promiseInfo ! == undefined) { maybeUnhandledPromises.delete(promise); if (promiseInfo.warned) { const { uid } = promiseInfo; // Generate the warning object early to get a good stack trace. // eslint-disable-next-line no-restricted-syntax const warning = new Error('Promise rejection was handled ' + `asynchronously (rejection id: ${uid})`); warning.name = 'PromiseRejectionHandledWarning'; warning.id = uid; ArrayPrototypePush(asyncHandledRejections, { promise, warning }); setHasRejectionToWarn(true); return; } } if (maybeUnhandledPromises.size === 0 && asyncHandledRejections.length === 0) setHasRejectionToWarn(false); } // If it's later than the Node.js event loop, the asyncHandledRejections array will be pushed, triggering the rejectionHandled event. function processPromiseRejections() { let maybeScheduledTicksOrMicrotasks = asyncHandledRejections.length > 0; while (asyncHandledRejections.length > 0) { const { promise, warning } = ArrayPrototypeShift(asyncHandledRejections); if (! process.emit('rejectionHandled', promise)) { process.emitWarning(warning); }}... promiseInfo.warned = true; . }Copy the code
- If the condition 3, 4 kPromiseResolveAfterResolved, established kPromiseRejectAfterResolved, call the resolveError (‘ resolve ‘promise, a tiny) method, Triggers the ‘multipleResolves’ event
function resolveError(type, promise, reason) {
// We have to wrap this in a next tick. Otherwise the error could be caught by
// the executed promise.
process.nextTick(() => {
process.emit('multipleResolves', type, promise, reason);
});
}
Copy the code
Conditions for triggering the multipleResolves event are as follows
- Resolve is more than once.
- Reject more than once.
- Resolve the reject.
- Resolve after reject.
setImmediate
Finally, what does the implementation of setImmediate mean, other than that it’s the sixth check of the event loop and the first timer of the event loop after setTimeout?
For immediateQueue, an Immediate instance was generated. The constructor for Immediate would send a record to append in its immediateQueue. The consumption process from immediateQueue is exactly the same as the consumption process from timerListQueue in setTimeout.
Calling the processImmediate function during the sixth check phase of the event loop registers the process to Libuv exactly the same as the processTimers mentioned above.
// lib/timers.js function setImmediate(callback, arg1, arg2, arg3) { validateCallback(callback); let i, args; switch (arguments.length) { // fast cases case 1: break; case 2: args = [arg1]; break; case 3: args = [arg1, arg2]; break; default: args = [arg1, arg2, arg3]; for (i = 4; i < arguments.length; I ++) {// Extend array dynamically, makes. Apply run much faster in v6.0.0args [i-1] = arguments[I]; } break; } return new Immediate(callback, args); }Copy the code
The processImmediate function exists primarily in the closure of the getTimerCallbacks function, and if you’re careful, you might notice that there’s an outstandingQueue cached outside of the function that serves the check phase of the event loop, If an error occurs in the callback function set by setImmediate and the while loop exits, the remaining data in the immediateQueue is not consumed.
Note that the try finally syntax below has no catch, so outstandingQueue can hold the remaining immediateQueue data without catching an error.
function getTimerCallbacks(runNextTicks) { const outstandingQueue = new ImmediateList(); function processImmediate() { const queue = outstandingQueue.head ! == null ? outstandingQueue : immediateQueue; let immediate = queue.head; if (queue ! == outstandingQueue) { queue.head = queue.tail = null; immediateInfo[kHasOutstanding] = 1; } let prevImmediate; let ranAtLeastOneImmediate = false; while (immediate ! == null) { if (ranAtLeastOneImmediate) runNextTicks(); else ranAtLeastOneImmediate = true; if (immediate._destroyed) { outstandingQueue.head = immediate = prevImmediate._idleNext; continue; } immediate._destroyed = true; immediateInfo[kCount]--; if (immediate[kRefed]) immediateInfo[kRefCount]--; immediate[kRefed] = null; prevImmediate = immediate; const asyncId = immediate[async_id_symbol]; emitBefore(asyncId, immediate[trigger_async_id_symbol], immediate); try { const argv = immediate._argv; if (! argv) immediate._onImmediate(); else immediate._onImmediate(... argv); } finally { immediate._onImmediate = null; if (destroyHooksExist()) emitDestroy(asyncId); outstandingQueue.head = immediate = immediate._idleNext; } emitAfter(asyncId); } if (queue === outstandingQueue) outstandingQueue.head = null; immediateInfo[kHasOutstanding] = 0; } function processTimers(now) { ... } function listOnTimeout(list, now) { ... } return { processImmediate, processTimers }; }Copy the code
As mentioned above, a callback like setImmediate, setTimeout registers, if an error is sent, what does the code do with that type of error?
Now we can talk about the implementation of catching unhandled errors that we often write in our code.
uncaughtException
process.on('uncaughtException', err => {
myReportFatalError(err)
})
Copy the code
The error of the interface from the v8 AddMessageListenerWithErrorLevel API, in nodejs call link below
- Call the v8 API set error monitoring function is PerIsolateMessageListener
// src/api/environment.cc void SetIsolateErrorHandlers(v8::Isolate* isolate, const IsolateSettings& s) { if (s.flags & MESSAGE_LISTENER_WITH_ERROR_LEVEL) isolate->AddMessageListenerWithErrorLevel( errors::PerIsolateMessageListener, Isolate::MessageErrorLevel::kMessageError | Isolate::MessageErrorLevel::kMessageWarning); . }Copy the code
- For the error to monitor function PerIsolateMessageListener at the wrong level
- Warning calls when ProcessEmitWarningGeneric, the function to invoke the process. The emit (‘ Warning ‘,…). To publish the warning event.
- TriggerUncaughtException is called on Error to publish an Error event.
// src/node_errors.cc
void PerIsolateMessageListener(Local<Message> message, Local<Value> error) {
Isolate* isolate = message->GetIsolate();
switch (message->ErrorLevel()) {
case Isolate::MessageErrorLevel::kMessageWarning: {
Environment* env = Environment::GetCurrent(isolate);
if (!env) {
break;
}
Utf8Value filename(isolate, message->GetScriptOrigin().ResourceName());
// (filename):(line) (message)
std::stringstream warning;
warning << *filename;
warning << ":";
warning << message->GetLineNumber(env->context()).FromMaybe(-1);
warning << " ";
v8::String::Utf8Value msg(isolate, message->Get());
warning << *msg;
USE(ProcessEmitWarningGeneric(env, warning.str().c_str(), "V8"));
break;
}
case Isolate::MessageErrorLevel::kMessageError:
TriggerUncaughtException(isolate, error, message);
break;
}
}
Copy the code
- TriggerUncaughtException mainly calls _fatalException on process.
// src/node_errors.cc void TriggerUncaughtException(Isolate* isolate, Local<Value> error, Local<Message> message, bool from_promise) { ... Local<Object> process_object = env->process_object(); Local<String> fatal_exception_string = env->fatal_exception_string(); Local<Value> fatal_exception_function = process_object->Get(env->context(), fatal_exception_string).ToLocalChecked(); . }Copy the code
- _fatalException function Settings link below, including createOnGlobalUncaughtException function, completes the uncaughtException event released ~
// lib/internal/bootstrap/node.js process._fatalException = onGlobalUncaughtException; // lib/internal/process/execution.js function createOnGlobalUncaughtException() { return (er, fromPromise) => { ... const type = fromPromise ? 'unhandledRejection' : 'uncaughtException'; process.emit('uncaughtExceptionMonitor', er, type); if (exceptionHandlerState.captureFn ! == null) { exceptionHandlerState.captureFn(er); } else if (! process.emit('uncaughtException', er, type)) { ... }... }Copy the code
summary
When I first ran the example, I found that the results were not consistent with the code expectations. After thinking about it, I switched the local node version to the latest version and ran the example again. The results and the code were as expected
Read more: github.com/xiaoxiaojx/… If you are interested, you can click “Star” for support. Thank you for reading ~