This article covers

  • Introduction of interview questions
  • The author has some questions about the execution order of the event cycle interview questions
  • In-depth understanding of microtasks, event loops, timers, etc. through interview questions
  • Conclusion total

The interview questions

The interview questions are as follows, you can try to write down the output first, and then watch my detailed explanation to see if there is any difference, if the whole order is clear node.js execution order should be no problem.

async function async1(){
    console.log('async1 start')
    await async2()
    console.log('async1 end')}async function async2(){
    console.log('async2')}console.log('script start')
setTimeout(function(){
    console.log('setTimeout0')},0)  
setTimeout(function(){
    console.log('setTimeout3')},3)  
setImmediate((a)= > console.log('setImmediate'));
process.nextTick((a)= > console.log('nextTick'));
async1();
new Promise(function(resolve){
    console.log('promise1')
    resolve();
    console.log('promise2')
}).then(function(){
    console.log('promise3')})console.log('script end')

Copy the code

Correct output of interview questions

script start
async1 start
async2
promise1
promise2
script end
nextTick
async1 end
promise3
setTimeout0
setImmediate
setTimeout3
Copy the code

Ask questions

Node.js is a single-threaded, asynchronous, non-blocking, and highly concurrent language. However, when node.js is implemented asynchronously, two asynchronous tasks are started. It is as simple as whoever is fast completes first. Or do asynchronous tasks also have a sequence of execution at the end? How does a single-threaded asynchronous language achieve high concurrency?

So let’s use these two questions to really understand asynchrony (microtasks and event loops) in Node.js.

Node’s asynchronous syntax is more complex than a browser’s because it can talk to the kernel and has to create a special library called Libuv to do this. The library is responsible for the execution time of the various callback functions, and the asynchronous tasks are eventually queued back to the main thread based on the event loop mechanism.

Detailed interpretation of the

1. Epicycle cycle and secondary cycle

Asynchronous tasks can be divided into two types.

  1. Appends asynchronous tasks in this loop
  2. Appends asynchronous tasks in the secondary loop

By “loop”, I mean the event loop. This is how the JavaScript engine handles asynchronous tasks, as explained later. Just understand that this cycle must be executed before the next cycle.

Node specifies that the process.nextTick and Promise callbacks are appended to this loop, which starts executing synchronization tasks as soon as they complete. The setTimeout, setInterval, and setImmediate callbacks append to the secondary loop.

2.process.nextTick()

1) Process. NextTick should not be treated as a secondary cycle by many friends just because there is next.

2) The Node completes all synchronization tasks and then executes the process.nextTick task queue.

3) If you want asynchronous tasks to execute as quickly as possible during development, you can do this using process.nexttick.

3. Microtack

According to the language specification, the Promise object’s callback function goes into the “microtask” queue within the asynchronous task.

The microtask queue appended to the process.nextTick queue is also part of the loop.

According to the language specification, the Promise object’s callback function goes into the “microtask” queue within the asynchronous task.

The microtask queue appended to the process.nextTick queue is also part of the loop. So, the following code always prints 3, then 4.

process.nextTick((a)= > console.log(3));
Promise.resolve().then((a)= > console.log(4));
Copy the code

// output result 3,4

process.nextTick((a)= > console.log(1));
Promise.resolve().then((a)= > console.log(2));
process.nextTick((a)= > console.log(3));
Promise.resolve().then((a)= > console.log(4));
Copy the code

// output 1,3,3,4

Note that the next queue is not executed until the previous queue has been emptied. The concept of two queues, nextTickQueue and microTaskQueue, that is to say, there are different types of asynchronous tasks that can be started, like promise objects, and then they go directly to the microqueue, and the one in the microqueue is the one that gets executed as soon as possible, However, for tasks that differ from queue to queue, there is still a precedence order, which is determined by the queue.

4. Phase of the event cycle (idle, prepare ignored this phase)

The most detailed explanation of the event cycle phase (official website: nodejs.org/en/docs/gui…

  1. Timers phase

    Phases include setTimeout() and setInterval()

  2. IO callbacks

    Most callback events are plain caollback

  3. Poll phase

    Network connection, data acquisition, reading files and other operations

  4. The check phase

    SetImmediate () calls the callback here

  5. Some close callbacks in the close phase, such as socket.on(‘close’,…)

  • Note the event loop

1) When Node starts executing the script, it initializes the event loop, but completes the following tasks before the event loop has started.

Synchronous tasks make asynchronous requests schedule when timers take effect execute process.nexttick (), etc

Finally, all of the above is done, and the cycle of events officially begins.

2) The event loop also runs in a single-threaded environment, and high concurrency also depends on the event loop. Every event generated will be added to the queue corresponding to the stage. At this time, the event loop will take out the event in the queue and prepare for the callback after execution.

3) Suppose that the event loop now enters a phase, and even if there are events in other queues ready during this period, all callback methods of the current queue are executed before proceeding to the next phase.

5. SetTimeOut and setImmediate in event loops

Since setTimeout is performed in the Timers phase, setImmediate is performed in the Check phase. So setTimeout is done before setImmediate.

setTimeout((a)= > console.log(1));
setImmediate((a)= > console.log(2));
Copy the code

The code above should print 1 and then 2, but when executed, the result is inconclusive, and sometimes 2 and then 1.

This is because the second argument to setTimeout defaults to 0. In fact, Node takes at least 1 millisecond, and the second parameter ranges from 1 millisecond to 2,147,483,647 milliseconds, according to the official documentation. In other words, setTimeout(f, 0) is the same as setTimeout(f, 1).

When you actually execute, once you get into the event loop, it might be 1 millisecond, or it might not be 1 millisecond, depending on what the system is doing. If less than 1 millisecond is reached, the Timers phase is skipped and proceeds to the Check phase, where the setImmediate callback function is first executed.

However, the following code must print 2 and then 1.

const fs = require('fs');
fs.readFile('test.js', () => {
 setTimeout((a)= > console.log(1));
 setImmediate((a)= > console.log(2));
});
Copy the code

The above code enters the I/O callbacks phase, then the Check phase, and finally the Timers phase. Therefore, setImmediate executes before setTimeout.

6. Some misunderstandings of Async and promise in synchronization tasks

  • Question 1:

In that interview question, if you have any questions, why async1 end after async2 output, instead of asynC1 end after ASYNC2 output?

Answer: Quote from Teacher Ruan Yifeng’s book: “Async function returns a Promise object. When the function is executed, once it encounters await, it will return first, wait until the triggered asynchronous operation is complete, and then execute the following statement in the function body.” To put it simply, the following synchronization task code is executed first. After completion of execution, that is, the Promise in the expression is resolved and the async function is continued to be executed and the solution result is returned. (This is actually a promise problem, and resolve is asynchronous, at the end of the loop.)

  • Question 2:

Why console.log(‘promise2’) is also executed before resolve?

A call to resolve or Reject does not terminate the promise function. Because immediate Resolved promises are the end of the cycle and always the synchronization task later than the current cycle. After a formal call to resolve or reject, the Promise is done, and subsequent actions should follow the then method. So it’s best to put a return statement in front of it so there are no surprises

new Promise((resolve,reject) = > {
    return resolve(1);
    // The following statement will not be executed
    console.log(2);
}
Copy the code
  • Question 3:

Is there any question about the execution order of promise3 and script end?

Answer: Because immediate Resolved promises are the end of the cycle and are always the synchronization tasks that are later in the cycle. Promise is a function that executes immediately, but its successful (or: reject) callback, resolve, executes asynchronously. When resolve() is executed, the task is placed in the callback queue, waiting for the event loop to pick it up when the call stack is free. The last one executed in this cycle.

The overall conclusion

The overall order is summed up as: synchronous tasks -> round -> round

Annex: Reference materials

Node. Js’s official website:

  • Event loop: nodejs.org/en/docs/gui…
  • Timers:nodejs.org/dist/latest…

Welcome everyone to pay attention to my public number — programmer growth refers to north. Please search by yourself on wechat — “Programmer growth refers to north”