• 1. An overview of the
  • 2. The call stack
  • 3. Event loops
  • 4. Microtask queues
  • 5. To summarize
  • 6. Reference links

1. An overview of the

Javascript is known to be a single-threaded language. This means that in Javascript, you can only do one thing at a time.

This design has some advantages, such as simplicity, avoiding complex state synchronization in multithreading, and writing programs without considering concurrent access. But it also brings up some other problems, one of which stands out: the code logic is not intuitive. Because Javascript is single-threaded, there is only one execution sequence. So, when performing asynchronous operations (such as timing, network requests, etc.), the Javascript runtime cannot be there waiting for the operation to complete. Otherwise, the entire runtime would be blocked in there, preventing all other operations, such as web rendering, user clicking, scrolling, etc. The user experience is terrible.

Because of this, Javascript uses callback functions to handle the results of asynchronous operations. A callback is passed in when an asynchronous operation is performed, and the Javascript engine executes the callback when the operation is complete, passing in the result. Slowly, Javascript is flooded with callbacks. Excessive use of callbacks makes a complete piece of logic split into many pieces, which is very difficult to read and maintain. The problem of too many callbacks is more pronounced in NodeJS, hence promises (see my previous blog) and async/await.

So how does the Javascript runtime sense and invoke the corresponding callback function when an asynchronous operation completes?

The answer is EventLoop.

To understand how EventLoop works, we first need to understand how Javascript handles task after task and calls function after function. That’s what the CallStack does.

2. The call stack

Readers with programming experience in other languages have probably heard of the concept of CallStack. CallStack in Javascript is similar.

CallStack is a stack structure, which is characterized by LIFO (last in first out), and the loading and unloading of the stack only occur at one end (the top).

The CallStack handles function calls and returns. Each time a function is called, Javascript runs to generate a new call structure that pushes into the CallStack. When the function call returns, the JavaScript runtime pops up the call structure at the top of the stack. Due to the LIFO nature of the stack, each pop-up must be the structure of the last function called.

When Javascript starts, the program is loaded from a file or standard input. When the load is complete, the Javascript runtime generates an anonymous function whose body is the input code. This function is somewhat similar to the main() function in C/C++, which is our entry function. Let’s call it the


function. When Javascript starts, the

function is called first. Look at the following code:

function func1() {
    console.log('in function1');
}

function func2() {
    func1();
    console.log('in function2');
}

function func3() {
    func2();
    console.log('in function3');
}

func3();
Copy the code

The above code is easy to understand. Let’s take a look at how Javascript runs this code.

Javascript first loads the code, creates an anonymous


wrapper around the code and calls the function.

the function executes, defining func1, func2, func3, and then calling func3. Create a call structure for func3 and push it. Func2 is called from func3, creating a call structure for func2 and pushing it. Func1 is called from func2, creating a call structure for func1 and pushing it. During this process, the CallStack changes as follows.

Function func1 then completes, popping the call structure from the top of the stack. Func2 then continues, and func2 pops its call structure from the top of the stack when it’s done. Func3 then continues, and func3 pops its call structure from the top of the stack when it’s done. During this process, the CallStack changes as follows.

Of course, there’s one thing I’m not very precise about here. Console. log is also a function. So when console.log is called from func1, there will be a corresponding CallStack on the CallStack. The same is true for the console.log call in func2, func3. If you are interested, you can draw the complete call flow yourself, so that you can deepen your understanding of πŸ˜€.

I recommend using Google Chrome developer tools to help you understand CallStack. Here is the result of executing the above code step by step in developer tools:

  • In Chrome,


    is called

    .

  • In a single step, observe the changes in the Call Stack column on the right toolbar.

A function that executes in the CallStack is called a task.

Next, consider the following question: how do setTimeout, setInterval, and AJAX requests work?

Philip Roberts once downloaded the source code for the V8 engine (Chrome’s built-in Javascript engine), grep it, and found that there was no code to implement these functions πŸ˜‚.

So how exactly are these functions implemented and how do they interact with the Javascript engine?

The answer is: the host (web browser, Node engine) provides the implementation and, when the operation is complete, puts the result (asynchronous, with latency) into the Javascript engine’s task queue for processing by the Javascript engine.

3. Event loops

An EventLoop, as its name suggests, is a loop in a Javascript engine that continually pulls tasks from a task queue.

Earlier we looked at the CallStack in detail and how it is handled when Javascript starts up. But after


exits, does the Javascript engine have nothing to do? Of course not, there are many tasks that trigger at random and require Javascript engines to handle. For example, user clicking buttons, timers, page rendering, etc.

In fact, the Javascript engine maintains a task queue. When no task is running in the CallStack, the engine removes the task from the task queue and pushes it into the CallStack for processing. Let’s look at the code in detail (quote Jesstelford) :

setTimeout(() => {
    console.log('hi');
}, 1000);
Copy the code

Our Js code, call Stack, Task Queue, and Web APIs (implemented in browsers) are as follows:

        [code]        |   [call stack]    | [task queue] | |    [Web APIs] |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | |               |
    console.log('hi') | | | | |}, 1000) | | | | | | | | | |Copy the code

At the beginning, the code is not executed, everything is empty.

        [code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <main>            |              | |               |
    console.log('hi') | | | | |}, 1000) | | | | | | | | | |Copy the code

To start executing the code, press in our


function.

        [code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
> setTimeout(() => {  | <main>            |              | |               |
    console.log('hi') | setTimeout        |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
Copy the code

Execute the first line, calling the function setTimeout. As we said earlier, each function call creates a new call record and pushes it onto the stack.

        [code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | <main>            |              | | timeout, 1000 |
    console.log('hi') | | | | |}, 1000) | | | | | | | | | |Copy the code

SetTimeout completes, removing the corresponding call record from the stack. The Web APIs log timeouts and callbacks, and the timeout mechanism is implemented by the browser.

        [code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | | timeout, 1000 |
    console.log('hi') | | | | |}, 1000) | | | | | | | | | |Copy the code

With no other logic in the code, the


function ends, removing the call information from the stack.

        [code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   | function   <-----timeout, 1000 |
    console.log('hi') | | | | |}, 1000) | | | | | | | | | |Copy the code

When the timeout expires, the Web APIs put the callback to the Task Queue.

        [code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function        <---function     | |               |
    console.log('hi') | | | | |}, 1000) | | | | | | | | | |Copy the code

EventLoop detects that there is no Javascript task processing and retrieves the task from the Task Queue.

        [code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function          |              | |               |
>   console.log('hi') | console.log       |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
Copy the code

Execute the callback, which in turn calls the console.log function.

        [code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  | function          |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
> hi
Copy the code

Console. log is executed, and “hi” is displayed.

        [code]        |   [call stack]    | [task queue] | |   [Web APIs]  |
  --------------------|-------------------|--------------| |---------------|
  setTimeout(() => {  |                   |              | |               |
    console.log('hi') |                   |              | |               |
  }, 1000)            |                   |              | |               |
                      |                   |              | |               |
> hi
Copy the code

When the callback completes, the CallStack is empty again.

This is the execution of setTimeout, where you begin to see what EventLoop is doing behind the scenes.

Let’s look at another piece of code:

console.log("start");

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

Promise.resolve()
  .then(() => {
      console.log("promise1");
  })
  .then(() => {
      console.log("promise2");
  });

console.log("end");
Copy the code

What is the output of this program? I suggest you think about it first, and it is best to draw a picture 😎.

4. Microtask queues

Following on from the code in the previous section, the standards-compliant (many older browser implementations are not standards-compliant, see Resources) output should look like this:

start
end
promise1
promise2
timeout
Copy the code

But, for? What? ?

In fact, there is another kind of queue in Javascript. The Promise callback is put into this queue. This queue is called a microtask queue, or Job queue in THE ES6 standard. Microtasks have a higher priority than tasks. That is, tasks in the microTask queue are processed first.

  • EventLoop detects that no task is currently running and first checks the MicroTask queue for any tasks that need to be processed. If so, execute one by one until the MicroTask queue is empty.

  • No task exists in the MicroTask queue. The task in the task queue is executed. Note that ** checks the status of the MicroTask queue every time it executes a task in the task queue. After all tasks in the MicroTask queue have been executed, remove the tasks from the task queue for execution.

Let’s see how the above code is executed step by step:

(For convenience we call the setTimeout callback timeoutCB, the first then successful callback as promisecB1, and the second then successful callback as promisecB2)

[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | 1. console.log("start"); 2 | | | | | | | | | | | | | | 3.setTimeout(() => {           |                   |              | |                   | |               |
  4.     console.log("timeout"); | | | | | | | 5.}, 0); | | | | | | | 6. | | | | | | | 7. Promise.resolve() | | | | | | | 8. .then(() => { | | | | | | | 9. console.log("promise1"); | | | | | | | 10.}) | | | | | | | 11.. then(() => { | | | | | | | 12. console.log("promise2"); |                   |              | |                   | |               |
  13.});                          |                   |              | |                   | |               |
  14.                             |                   |              | |                   | |               |
  15.console.log("end");          |                   |              | |                   | |               |
Copy the code

To start, the Call Stack, Task Queue, and MicroTask Queue are all empty.

[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | > 1. console.log("start");         |     <main>        |              | |                   | |               |
  2.                               |     console.log   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout"); | | | | | | | 5.}, 0); | | | | | | | 6. | | | | | | | 7. Promise.resolve() | | | | | | | 8. .then(() => { | | | | | | | 9. console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2"); | | | | | | | 13.}); | | | | | | | 14. | | | | | | | 15. console.log("end");          |                   |              | |                   | |               |
Copy the code

The program starts running, pressing the


function. The first line of code, console.log(“start”), is executed to push console.log.

[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | > 1. console.log("start");         |     <main>        |              | |                   | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout"); | | | | | | | 5.}, 0); | | | | | | | 6. | | | | | | | 7. Promise.resolve() | | | | | | | 8. .then(() => { | | | | | | | 9. console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2"); | | | | | | | 13.}); | | | | | | | 14. | | | | | | | 15. console.log("end");          |                   |              | |                   | |               |
> start
Copy the code

When the console.log command is executed, start is displayed.

[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | 1. console.log("start");         |    <main>         |              | |                   | |               |
  2.                               |    setTimeout     |              | |                   | |               |
> 3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout"); | | | | | | | 5.}, 0); | | | | | | | 6. | | | | | | | 7. Promise.resolve() | | | | | | | 8. .then(() => { | | | | | | | 9. console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2"); | | | | | | | 13.}); | | | | | | | 14. | | | | | | | 15. console.log("end");          |                   |              | |                   | |               |
> start
Copy the code

The second action skips empty and starts executing the third line of code, setTimeout pressing the stack.

[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | 1. console.log("start");         |    <main>         |   timeoutcb <------------------------~~timeoutcb, 0~~|
  2.                               |                   |              | |                   | |               |
> 3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout"); | | | | | | | 5.}, 0); | | | | | | | 6. | | | | | | | 7. Promise.resolve() | | | | | | | 8. .then(() => { | | | | | | | 9. console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2"); | | | | | | | 13.}); | | | | | | | 14. | | | | | | | 15. console.log("end");          |                   |              | |                   | |               |
> start
Copy the code

SetTimeout completes execution. Since the timeout is 0, the immediate callback enters the Task Queue immediately.

[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | 1. console.log("start");         |    <main>         |  timeoutcb   | |     promisecb1    | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout"); | | | | | | | 5.}, 0); | | | | | | | 6. | | | | | | | > 7. Promise.resolve() | | | | | | | 8. .then(() => { | | | | | | | 9. console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2"); | | | | | | | 13.}); | | | | | | | 14. | | | | | | | 15. console.log("end");          |                   |              | |                   | |               |
> start
Copy the code

Line 7:

  • Resolve is first pressed, and a Promise object is returned when the execution is complete.

  • The then method of that object is then called, which pushes the stack and returns a brand new Promise object that we call Promise1. Because Promise.resolve returns an object in the resolved state, the promise1 callback goes directly to the MicroTask queue.

  • Then we execute the THEN method of the new object, which we call promise2.

[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | 1. console.log("start");         |    <main>         |    timeout   | |     promisecb1    | |               |
  2.                               |    console.log    |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout"); | | | | | | | 5.}, 0); | | | | | | | 6. | | | | | | | 7. Promise.resolve() | | | | | | | 8. .then(() => { | | | | | | | 9. console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2"); | | | | | | | 13.}); | | | | | | | 14. | | | | | | | > 15. console.log("end");          |                   |              | |                   | |               |
> start
Copy the code

At line 15, console.log presses the stack.

[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | 1. console.log("start");         |    <main>         |    timeout   | |     promisecb1    | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout"); | | | | | | | 5.}, 0); | | | | | | | 6. | | | | | | | 7. Promise.resolve() | | | | | | | 8. .then(() => { | | | | | | | 9. console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2"); | | | | | | | 13.}); | | | | | | | 14. | | | | | | | > 15. console.log("end");          |                   |              | |                   | |               |
> start
> end
Copy the code

Console. log(“end”) completes execution, outputs “end”, and exits the stack.

[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | 1. console.log("start");         |                   |    timeout   | |     promisecb1    | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout"); | | | | | | | 5.}, 0); | | | | | | | 6. | | | | | | | 7. Promise.resolve() | | | | | | | 8. .then(() => { | | | | | | | 9. console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2"); | | | | | | | 13.}); | | | | | | | 14. | | | | | | | > 15. console.log("end");          |                   |              | |                   | |               |
> start
> end
Copy the code

The


function has no more logic to execute.

[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | 1. console.log("start");         |    promisecb1     |    timeout   | |                   | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout"); | | | | | | | 5.}, 0); | | | | | | | 6. | | | | | | | 7. Promise.resolve() | | | | | | | 8. .then(() => { | | | | | | | 9. console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2"); | | | | | | | 13.}); | | | | | | | 14. | | | | | | | > 15. console.log("end");          |                   |              | |                   | |               |
> start
> end
Copy the code

EventLoop checks that a task needs to be executed in the MicroTask queue and pushes the promisecB1 onto the Call stack.

[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | 1. console.log("start");         |                   |    timeout   | |      promisecb2   | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout"); | | | | | | | 5.}, 0); | | | | | | | 6. | | | | | | | 7. Promise.resolve() | | | | | | | 8. .then(() => { | | | | | | | 9. console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2"); | | | | | | | 13.}); | | | | | | | 14. | | | | | | | 15. console.log("end");          |                   |              | |                   | |               |
> start
> end
> promise1
Copy the code

Promisecb1 is executed, “promise1” is displayed, and undefined is returned. (In fact, there is a console.log push out process, I won’t draw, the same as below)

In a deeper look at Javascript promises, if a value is returned, the object immediately becomes resolved. So the callback for the second THEN needs to be scheduled to execute into the MicroTask queue.

[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | 1. console.log("start");         |     promisecb2    |    timeout   | |                   | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout"); | | | | | | | 5.}, 0); | | | | | | | 6. | | | | | | | 7. Promise.resolve() | | | | | | | 8. .then(() => { | | | | | | | 9. console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2"); | | | | | | | 13.}); | | | | | | | 14. | | | | | | | 15. console.log("end");          |                   |              | |                   | |               |
> start
> end
> promise1
Copy the code

EventLoop detects that there are no ongoing tasks in the Call Stack and that the MicroTask queue is not empty. Pull the task from the MicroTask queue and push it onto the Call Stack.

[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | 1. console.log("start");         |                   |    timeout   | |                   | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout"); | | | | | | | 5.}, 0); | | | | | | | 6. | | | | | | | 7. Promise.resolve() | | | | | | | 8. .then(() => { | | | | | | | 9. console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2"); | | | | | | | 13.}); | | | | | | | 14. | | | | | | | 15. console.log("end");          |                   |              | |                   | |               |
> start
> end
> promise1
> promise2
Copy the code

Promisecb2 The command is executed, and “promise2” is displayed.

[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | 1. console.log("start");         |    timeoutcb      |              | |                   | |               |
  2.                               |                   |              | |                   | |               |
  3. setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout"); | | | | | | | 5.}, 0); | | | | | | | 6. | | | | | | | 7. Promise.resolve() | | | | | | | 8. .then(() => { | | | | | | | 9. console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2"); | | | | | | | 13.}); | | | | | | | 14. | | | | | | | 15. console.log("end");          |                   |              | |                   | |               |
> start
> end
> promise1
> promise2
Copy the code

Next,EventLoop detects that both the Call Stack and the MicroTask queue are empty and pushes the TimeoutCB from the Task queue.

[code] | [call stack] | [task queue] | | [microtask queue] | | [Web APIs] | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | | -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | 1. console.log("start"); 2 | | | | | | | | | | | | | | 3.setTimeout(() => {            |                   |              | |                   | |               |
  4.     console.log("timeout"); | | | | | | | 5.}, 0); | | | | | | | 6. | | | | | | | 7. Promise.resolve() | | | | | | | 8. .then(() => { | | | | | | | 9. console.log("promise1");  |                   |              | |                   | |               |
  10. })                           |                   |              | |                   | |               |
  11. .then(() => {                |                   |              | |                   | |               |
  12.    console.log("promise2"); | | | | | | | 13.}); | | | | | | | 14. | | | | | | | 15. console.log("end");          |                   |              | |                   | |               |
> start
> end
> promise1
> promise2
> timeout
Copy the code

Timeoutcb After the command is executed, “timeout” is displayed.

5. To summarize

In this article, we learned how Javascript handles function calls and returns using the Call Stack. Asynchronous mechanisms such as setTimeout are actually implemented by the host, and the host is responsible for putting the callback into the task queue after the asynchronous operation is completed. Finally, EventLoop takes out the callback and presses it into the Call stack for actual execution at the appropriate time.

We also saw another type of queue, the MicroTask queue. This queue typically houses higher-priority tasks, such as Promise’s callback handler.

Whenever there are no ongoing tasks in the Call Stack, EventLoop preferentially pulls the task from the MicroTask queue for execution, and only pulls it from the Task queue when the queue is empty.

When working with a framework or language, there is often a big debate about the need to understand the underlying mechanics and principles. Some people say, I can write good programs without understanding the internals, so why spend time studying them? In this regard, I think it is necessary to understand the underlying principles. It has the following advantages:

  • It allows us to see the big picture, how the whole system works.

  • The underlying principles are mostly the same, for example, almost all function calls in languages are implemented by CallStack. Learn Javascript CallStack operation mechanism, in learning related concepts in other languages can often get twice the result with half the effort.

  • Knowing the ground floor gives us an idea of what we can and can’t do. For example, Javascript is single-threaded, and we must not let the thread block πŸ˜€ when we write code.

  • Understanding the basics allows us to optimize our code better. When application performance bottlenecks occur, problems can be located more quickly.

6. Reference links

  1. Philip Roberts’ Talk at the European JSConf (must See)
  2. What is the JS Event Loop and Call Stack? β€” Jess Telford
  3. Tasks, microtasks, queues and schedules
  4. Understanding the JavaScript call stack

About me: Personal homepage Simple book nuggets