Process is a global object that provides information about the current Node.js thread and some control methods. Because process mounts too many properties and methods, this article will start with process.nexttick ().

setupNextTick

Function setupNextTick () {/ / set Promise module scheduling method is const promises = the require (' internal/process/promises'); const emitPendingUnhandledRejections = promises.setup(scheduleMicrotasks); var nextTickQueue = []; Var microtasksScheduled = false; Var _runMicrotasks = {}; Var kIndex = 0; var kIndex = 0; var kIndex = 0; var kLength = 1; process.nextTick = nextTick; // Needs to be accessible from beyond this scope. process._tickCallback = _tickCallback; process._tickDomainCallback = _tickDomainCallback; // Register _tickCallback with process._setupNexttick to get _runMicrotasks // 'tickInfo' also receives the return parameter of 'process._setupNexttick ()', TickInfo enables C++ modules to access the state of the nextTick queue. const tickInfo = process._setupNextTick(_tickCallback, _runMicrotasks); / / receive driver V8 's method of micro task queue _runMicrotasks = _runMicrotasks. RunMicrotasks; function tickDone() { ... } function scheduleMicrotasks() { ... } function runMicrotasksCallback() { ... } function _combinedTickCallback() { ... } function _tickCallback() { ... } function _tickDomainCallback() { ... } function nextTick() { ... }}Copy the code

_tickCallback & _tickDomainCallback

Both of these generally execute a certain number of callbacks (up to 1E4), and the former does not need to execute a domain into context.

function _tickCallback() { var callback, args, tock; do { while (tickInfo[kIndex] < tickInfo[kLength]) { tock = nextTickQueue[tickInfo[kIndex]++]; callback = tock.callback; args = tock.args; _combinedTickCallback(args, callback); if (kMaxCallbacksPerLoop < tickInfo[kIndex]) tickDone(); } tickDone(); // V8 promise microtasks _runMicrotasks(); emitPendingUnhandledRejections(); } while (tickInfo[kLength] ! = = 0); }Copy the code

_combinedTickCallback

Again, the parameter handling here reflects Nodejs’ performance drive and 80/20 ethos.

function _combinedTickCallback(args, callback) { if (args === undefined) { callback(); } else { 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; default: callback.apply(null, args); }}}Copy the code

tickDone

Perform normal cleanup operations, either deleting the callback that just finished or emptying the queue.

function tickDone() { if (tickInfo[kLength] ! == 0) { if (tickInfo[kLength] <= tickInfo[kIndex]) { nextTickQueue = []; tickInfo[kLength] = 0; Nexttickqueue.splice (0, tickInfo[kIndex]); tickInfo[kLength] = nextTickQueue.length; } } tickInfo[kIndex] = 0; }Copy the code

scheduleMicrotasks

Let’s go back to promises. Setup (scheduleMicrotasks) in setupNextTick. Let’s look at scheduleMicrotasks.

function setupNextTick() { const promises = require('internal/process/promises'); const emitPendingUnhandledRejections = promises.setup(scheduleMicrotasks); var microtasksScheduled = false; . }Copy the code

The nextTickQueue is scheduled. If false, the nextTickQueue is scheduled. Callback is runMicrotasksCallback. TickInfo [kLength] is then incremented by 1 and microtasksScheduled is set to true, ensuring that the MicroTask will not be repeated until it has been executed.

function scheduleMicrotasks() {
    if (microtasksScheduled)
      return;

    nextTickQueue.push({
      callback: runMicrotasksCallback,
      domain: null
    });

    tickInfo[kLength]++;
    microtasksScheduled = true;
  }Copy the code

runMicrotasksCallback

As you can see here, microtasksScheduled = false is executed first, followed by _runMicrotasks. Continue calling scheduleMicrotasks to add callback to nextTickQueue while the nextTickQueue and Promise Listeners are still available.

function runMicrotasksCallback() {
    microtasksScheduled = false;
    _runMicrotasks();

    if (tickInfo[kIndex] < tickInfo[kLength] ||
        emitPendingUnhandledRejections())
      scheduleMicrotasks();
  }Copy the code

SetupNextTick

As we know from the JS section above, Process. nextTick, Microtasks, and Promise callbacks are all scheduled through a queue called nextTickQueue, And it all starts with _tickCallback (_tickDomainCallback).

// src/node.cc void SetupNextTick(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsFunction()); CHECK(args[1]->IsObject()); Tick_callback_function env->set_tick_callback_function(args[0].as <Function>()); Env ->SetMethod(args[1].As<Object>(), "runMicrotasks", runMicrotasks);  _setupNextTick env->process_object()->Delete(env->context(), FIXED_ONE_BYTE_STRING(args.GetIsolate(), "_setupNextTick")).FromJust(); Uint32_t * const fields = env->tick_info()->fields(); uint32_t const fields_count = env->tick_info()->fields_count(); Local<ArrayBuffer> array_buffer = ArrayBuffer::New(env->isolate(), fields, sizeof(*fields) * fields_count); / / returns an array [0, 0] / / and lib/internal/process/next_tick. / / kIndex in js, Set(Uint32Array::New(array_buffer, 0, fields_count)); }Copy the code

RunMicrotasks () is https://cs.chromium.org/chromium/src/v8/src/api.cc?q=RunMicrotasks&dr=CSs&l=8512 v8 exposed an API method

If tick_callback_function is set above, when will process.nexttick () be called?

AsyncWrap

// src/async-wrap.cc Local<Value> AsyncWrap::MakeCallback(const Local<Function> cb, int argc, Local<Value>* argv) { ... Local<Value> ret = cb->Call(context, argc, argv); . Environment::TickInfo* tick_info = env()->tick_info(); // Run RunMicrotasks if (tick_info->length() == 0) {env()-> ISOLATE ()->RunMicrotasks(); } Local<Object> process = env()->process_object(); if (tick_info->length() == 0) { tick_info->set_index(0); return ret; _tickCallback if (env()->tick_callback_function()->Call(process, 0) nullptr).IsEmpty()) { return Local<Value>(); } return ret; }Copy the code

HandleWrap

// - MakeCallback may only be made directly off the event loop. // That is there can be no JavaScript stack frames Underneath it. // MakeCallback is executed directly in the Event loop. class HandleWrap : public AsyncWrap { public: ... uv_handle_t* const handle_; };Copy the code

For example, UDPWrap is the wrapper layer of Nodejs User Datagram Protocol (UDP), which inherits from HandleWrap

// src/udp_wrap.cc
class UDPWrap: public HandleWrap {
 public:
  ...
 private:
  ...
  uv_udp_t handle_;
};Copy the code

AsyncWrap is the most IO wrapper layer in Nodejs based on HandleWrap. HandleWrap inherits from AsyncWrap, so process.nextTick and microtask are basically called in the uv__io_poll phase, why primary, because there are two other cases, moving on.

The node initialization

Node initialization calls process._tickcallback ()

// lib/module.js

// bootstrap main module.
Module.runMain = function() {
  // Load the main module--the command line argument.
  Module._load(process.argv[1], null, true);
  // Handle any nextTicks added in the first tick of the program
  process._tickCallback();
};Copy the code

Catch exceptions

If an exception is thrown in the application, the thread will exit if it is not caught. If it is caught, the application will not crash and exit. Instead, setImmediate is called to perform process._tickcallback (), which means that process.nexttick may also be called during the Check phase.

function setupProcessFatal() { process._fatalException = function(er) { var caught; if (process.domain && process.domain._errorHandler) caught = process.domain._errorHandler(er) || caught; if (! caught) caught = process.emit('uncaughtException', er); // if there is no function to handle this exception, C++ ends up with if (! caught) { try { if (! process._exiting) { process._exiting = true; process.emit('exit', 1); }} catch (er) {// nothing to be done about it at this point.}} else { Invoking _tickCallback() in 'setImmediate' continues processing the nextTick queue NativeModule.require('timers').setImmediate(process._tickCallback); } return caught; }; }Copy the code

scheduleMicrotasks?

function scheduleMicrotasks() {
    if (microtasksScheduled)
      return;

    nextTickQueue.push({
      callback: runMicrotasksCallback,
      domain: null
    });

    tickInfo[kLength]++;
    microtasksScheduled = true;
}Copy the code

MakeCallback

// src/node.cc Local<Value> MakeCallback() { ... // V8 RunMicrotasks if (tick_info->length() == 0) { env->isolate()->RunMicrotasks(); }... return ret; }Copy the code

Promise

Microtask runs automatically by default in V8. Because the asynchronous scenarios Promise handles are closely related to asynchronous IO in most Nodejs, automatic running is turned off in Nodejs by default and RunMicrotasks () is triggered by Nodejs itself. Using the code above, you can basically conclude that the execution phases of Promise and process.Nexttick () callbacks in Nodejs are similar.

inline int Start(..) {... isolate->SetAutorunMicrotasks(false); .Copy the code

conclusion

Process.nexttick () is usually executed in the poll phase, but may also be executed in the check phase. The Microtasks on which the Promise is placed are executed by calling the V8 exposed RunMicrotasks() method, which is executed in the process.Nexttick () queue, It is also executed in node::MakeCallback.

The resources

lib/internal/process/next_tick.js

src/node.cc

lib/internal/process/promises.js

/src/handle_wrap.h

chromium/src/v8/src/api.cc