What is an event loop

As we all know, JavaScript is single threaded, and Nodejs can implement non-blocking I/O operations because of the Event Loop.

Event Loop mainly has the following stages, one rectangle represents one stage, as shown below:

┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┌ ─ > │ timers │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ I/O callbacks │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ idle, Prepare │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ incoming: │ │ │ poll │ < ─ ─ ─ ─ ─ ┤ connections, │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ data, Etc. │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ │ check │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┬ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ├ ──┤ close callbacks ────── our r companyCopy the code

Each phase maintains a first-in, first-out queue structure, and when the event loop enters a phase, certain actions for that phase are performed, followed by the callback functions in the queue. The next phase occurs when the number of callback functions in the queue or the number of callback functions executed reaches a certain maximum.

timers

At the beginning of the event loop, setTimeout and setInterval callbacks are executed.

When the timer expires, the timer callback is queued and executed in sequence.

Suppose you have four timers A, B and C, with time intervals of 10ms, 20ms and 30ms respectively. When entering the timer phase of the event loop, 25 ms passes, the callbacks of timers A and B are executed and the next phase is entered.

I/O callbacks

Perform callbacks except for setTimeout, setInterval, setImmediate, and close Callbacks.

idle, prepare

Do some internal operations.

poll

This is probably the most important stage in the cycle of events.

If the queue is not empty at this stage, the callbacks in the queue are executed sequentially; If the queue is empty, the setImmediate function is also called, and the check phase is entered. If the queue is empty and there is no setImmediate function call, the event loop waits and executes as soon as a callback is added to the queue.

check

The setImmediate callback is executed at this stage.

close callbacks

For example socket. On (‘ close ‘,…). The callback to etc is executed at this stage.

setTimout vs setImmediate

// timeout_vs_immediate_1.js
setTimeout(function timeout() {
  console.log('timeout');
}, 0);

setImmediate(function immediate() {
  console.log('immediate');
});
Copy the code

According to the previous version, the event loop enters the Timer phase, performing the setTimeout callback, and the setImmediate callback is not executed until the Check phase is reached. So some people think the output of the above code should be:

$ node timeout_vs_immediate_1.js
timeout
immediate
Copy the code

But the result here is actually inconclusive. This often depends on the performance of the process, and the setTimeout interval, although 0, will actually be 1. So when the initiator enters the event loop and the time has not passed 1ms, the queue in the timer phase is empty and no callback is executed. And then the setImmediate function is called, so the setImmediate callback is called when you go to the Check phase. If the timer loop reaches the timer phase, the setTimeout callback is performed, and the Check phase is followed by the setImmediate callback.

So, either of the following outputs can occur.

$ node timeout_vs_immediate_1.js
timeout
immediate

$ node timeout_vs_immediate_1.js
immediate
timeout
Copy the code

Suppose the above code is placed in an I/0 loop, as in

// timeout_vs_immediate_2.js
const fs = require('fs');

fs.readFile(__filename, () => {
  setTimeout((a)= > {
    console.log('timeout');
  }, 0);
  setImmediate((a)= > {
    console.log('immediate');
  });
});
Copy the code

Then the result is definite and the output is as follows

$ node timeout_vs_immediate_2.js
immediate
timeout
Copy the code

process.nextTick()

Process.nexttick () is not part of the event loop, but is also an asynchronous API.

After the current operation is complete, if process.nexttick () is called, then the callback in process.nexttick () will be executed. If process.nexttick () is called, then the callback will be executed. The process.nexttick () callback in the callback is then executed. So procee.nexttick () might block the event loop to the next stage.

// process_nexttick_1.js
let i = 0;

function foo() {
    i += 1;

    if (i > 3) return;

    console.log('foo func');

    setTimeout((a)= > {
        console.log('timeout');
    }, 0);

    process.nextTick(foo);
}

setTimeout(foo, 5);
Copy the code

As previously stated, the output above is as follows:

$ node process_nexttick_1.js
foo func
foo func
foo func
timeout
timeout
timeout
Copy the code

You may wonder if process.nexttick () is executed after all the queues in the event loop have been emptied, or after a callback in the queue has been executed. What is the output of the following code?

// process_nexttick_2.js
let i = 0;

function foo() {
    i += 1;

    if (i > 2) return;

    console.log('foo func');

    setTimeout((a)= > {
        console.log('timeout');
    }, 0);

    process.nextTick(foo);
}

setTimeout(foo, 2);
setTimeout((a)= > {
    console.log('another timeout');
}, 2);
Copy the code

Let’s do it and see what happens

/ / the node version: v11.12.0
$ node process_nexttick_2.js
foo func
foo func
another timeout
timeout
timeout
Copy the code

As a result, process.nexttick () is executed after a callback in the queue completes, as shown above.

You can see the result above, with a comment node version: v11.12.0. This means that the node version running this code is 11.12.0. If your Node version is lower than this, such as 7.10.1, you may get different results.

/ / the node version: v7.10.0
$ node process_nexttick_2.js
foo func
another timeout
foo func
timeout
timeout
Copy the code

Different versions behave differently. I think the new version has been updated and adjusted.

process.nextTick() vs Promise

Process.nexttick () corresponds to nextTickQueue and Promise corresponds to microTaskQueue.

Neither of these are part of the event loop, but they are executed after the current action

// process_nexttick_vs_promise.js
let i = 0;

function foo() {
    i += 1;

    if (i > 2) return;

    console.log('foo func');

    setTimeout((a)= > {
        console.log('timeout');
    }, 0);

    Promise.resolve().then(foo);
}

setTimeout(foo, 0);

Promise.resolve().then((a)= > {
    console.log('promise');
});

process.nextTick((a)= > {
    console.log('nexttick');
});

Copy the code

Run the code and the result is as follows:

$ node process_nexttick_vs_promise.js
nexttick
promise
foo func
foo func
timeout
timeout
Copy the code

If you understand the output above, you should be able to understand the event loop in Nodejs.

The resources

  • The Node.js Event Loop, Timers, and process.nextTick()
  • Node.js event loop workflow & lifecycle in low level

discuss

The original address

Welcome everyone to discuss together, there is a good place please correct.