It is well known that JavaScript language is a single-threaded language. Single-threaded means that all tasks need to be executed in order, and the next task can only be executed after the last task is finished. What is the execution mechanism of JavaScript? Let’s take a look at each of these in code.

Call stacks and task queues in JavaScript

To better understand the call stack and task queue in JavaScript, take a look at the following figure (paraphrase from Philip Roberts’ talk “Help, I’m Stuck in an Event-Loop”) :

The diagram above shows that the main thread generates heap and stack during execution. While one task in the execution environment’s stack is executing, all other tasks are in a waiting state. When the main process performs an asynchronous operation, the corresponding task callback is placed in the corresponding task queue. When all tasks in the call stack of the main process are completed, the task in the task queue (callback function) is executed. As follows:

Example 1:

  console.log(1);
  function test() {
      setTimeout(function () {
          console.log('test');
      })
  }
  test();
  console.log(3);
  // Result: 1. 3. Test
Copy the code

The above code is executed as shown below:

First, the main function of the execution context is pushed into the execution environment stack, and then console.log(1) is executed sequentially. Console.log (1) is a task in the execution environment stack, and the task is executed, printing 1, and the code continues to execute, resulting in the following environment:

A test task is added to the stack of execution environments, so a test function is declared and the code continues, as shown below:

The task created by setTimeout is in a waiting state, so the console outputs 3. The console outputs 1 and 3. At this point, all tasks in the execution environment stack have been executed, and control appears in the execution environment stack. At this time, the task in the task queue will be checked to see if there is any task that needs to be executed. At this time, the task created by setTimeout will be found, and the execution function of this task will be added to the callback queue. Since there is no task in the execution environment stack, the change callback function will be executed in the execution environment stack, as shown in the following figure:

At this point, the task in the execution environment stack will start to execute, and then ‘test’ will be printed. After the output is completed, no task exists in the execution environment stack, task queue, or callback queue, and the whole process is completed.

However, task queues are divided into macro-tasks and micro-tasks.

  1. Macro-task includes: Script (overall code), setTimeout, setInterval, setImmediate, I/O, AND UI Rendering.
  2. Micro-tasks include: Process. NextTick, Promises, Object. Observe, MutationObserver

The order of the event loop is from script through the first loop, then the global context enters the function call stack, passes it to the module that processes macro-Task, and then puts the callback function in the macro-Task queue. When a Micro-Task is encountered, its callback function is also put into the queue of the Micro-Task. The global execution context is left until the function call stack is cleared, and then all micro-tasks are executed. When all executable micro-tasks have been executed. The loop executes a task queue in macro-Task again, then executes all of the micro-tasks, and so on.

I’ll use Process. nextTick, Promise,setImmediate, and setTimeout as examples. The code is as follows:

   setTimeout(function () {
       console.log(1);
   },0);
   console.log(2);
   process.nextTick((a)= > {
       console.log(3);
   });
   new Promise(function (resolve, rejected) {
       console.log(4);
       resolve()
   }).then(res= >{
       console.log(5);
   })
   setImmediate(function () {
       console.log(6)})console.log('end');
Copy the code

With the previous execution analysis, the above code is divided into code blocks as shown below:

Continue with code 2 — console.log(2); The console then prints 2, as shown:

Next, execute 3 — process.nextTick. Since process.nextTick is a micro-task, add the callback function of that task to the micro-Task queue.

The console output is still just 2, followed by the execution of 4-PROMISE. When new Promise is executed, when the Promise instance is created, the function passed in will be executed in the execution environment stack, so 4 will be printed in the console, and then will be added to the micro-Task task queue. Form the following picture:

The mediate mediate function is then added to the Macro Task queue, resulting in the following image:

Continue with 6-console, adding console.log(‘end ‘) to the stack, and the console prints 6, as shown below:

At this point, there are no more tasks in the execution environment stack of the main thread, so the Event Loop mechanism will start to execute the one (3-callback) in the micro-Task queue that meets the execution conditions in the execution environment stack, forming the following figure:

Then output 3 will appear on the console. After the task execution of the micro-Task queue is completed, the same principle will be applied to the (4-then) meeting the conditions again in the execution environment stack, forming the following figure:

At this time, the micro-task task queue will be completed, and the Event Loop mechanism will be used to execute the macrotask task queue to the execution environment stack, which is the same principle as the micro-task queue. The 1-callback is executed in the execution environment, and the console outputs 1. After execution, the 5-callback is executed in the execution environment stack, and the console outputs 6. So the final console output is: 2, 4, end, 3, 5, 1, 6;

Conclusion:

  1. Synchronization tasks are performed on the execution environment stack of the main thread first, and then the Event Loop mechanism is used to continuously Loop each task in the task queue into the execution environment stack.

  2. Tasks are divided into macro-task and micro-task, each of which has its own task queue, namely macro-task task queue and micro-task task queue.

  3. The total order of execution is task — micro-task — macro-task on the main thread;

References:

More on the Event Loop

JavaScript event loop (2)

The node Chinese website