Writing in the front

Js is a single-threaded programming language, that is to say, when JS processes tasks, all tasks can only be executed in a queue on one thread. What if a task takes a long time? You can’t wait until it’s done to execute the next one. So within the thread, it is divided into two queues:

  • Synchronizing task queue
  • Asynchronous task queue

For example, if you go to the bank for business, you need to queue up for your number. Bank teller one by one for business, then the teller is equivalent to a JS thread, the queue of customers is equivalent to synchronous task queue, each person for the teller is equivalent to a task. But then your phone rings and you answer it for half an hour. At this time the member of the other family cupboard sees you this circumstance, called directly next, and the number that you get became invalid, can re – line up zero number only. You are sent to an asynchronous task queue. When everyone in front of you is finished and the teller calls you over to do your business, the synchronous queue is finished and the main thread will process the asynchronous queue.

Synchronous and asynchronous tasks

By asynchronous tasks, it means that there are macro and micro tasks that are independent of the main execution stack.

Let’s start with a simple example to get a sense of how this works:

console.log('start')

console.log('end')
Copy the code

If you print “start” and then “end”, the code will enter the synchronization queue and be executed sequentially.

So let’s spice it up:

console.log('start')

setTimeout(function() {
    console.log('setTimeout')
}, 0)

console.log('end')
Copy the code

In this case, when the function call stack reaches setTimeout, setTimeout will put the callback function into the asynchronous queue at the specified time point, and wait for the synchronization queue to complete the task execution, and then execute immediately, so the result is: start, end, and setTimeout.

But one thing to note is that it is generally believed that setTimeout timed execution is one-sided, because suppose setTimeout specifies execution after 2 seconds, but there is a function in the synchronization queue that takes a long time to execute, or even 1 second. In this case, the setTimeout callback will wait at least 1 second for the synchronization task to complete before executing. The setTimeout callback will execute for more than 2 seconds, or at least 3 seconds.

Macro and micro tasks

Macrotasks and microtasks are two other queues separate from the main execution stack and can be conceptually divided into asynchronous task queues. These queues are handled by JS eventloops.

Macro-task and micro-task are referred to as Task and Jobs respectively in the latest standard.

I didn’t notice that the concept of macro task and micro task is actually inaccurate when I wrote the article. Thank you for pointing it out in the comments. However, since the article involves multiple interpretations of macro task and micro task, this paper still refers to task and jobs by macro task and micro task respectively for the time being. However, the reader should understand that there is no macro task in the specification, only task and jobs

Macro tasks include:

  • Script (whole code)
  • setTimeout, setInterval, setImmediate,
  • I/O
  • Ajax requests are not macro tasks. When js threads encounter Ajax requests, they will hand the request to the corresponding HTTP thread for processing. Once the request returns the result, they will put the corresponding callback into the macro task queue and wait for the request to complete execution.

Jobs include:

  • process.nextTick
  • Promise
  • Object. Observe (deprecated)
  • MutationObserver(new html5 feature)

These can be interpreted as executable code in the execution context, executing immediately, but putting their respective callback functions into the corresponding task queue (macro task micro task), which acts as a scheduler.

Let’s review the execution mechanism of the event loop: the loop starts with macro task, encounters script, generates execution context, and enters the execution stack. The executable code is pushed onto the stack, executes the code in turn, and calls are called off the stack. When the scheduler mentioned above is encountered during execution, the scheduler will be executed synchronously, and the scheduler will put its responsible task (callback function) in the corresponding task queue until the main execution stack is empty, and then the task queue of micro-tasks will be executed. After the microtask is also cleared, the loop starts again with the macro task and continues through the process.

The sample

So with all that said, let’s do a little bit of code to see if this is the case, but let’s do a simpler one.

console.log('start')

setTimeout(function() {
    console.log('timeout')
}, 0)

new Promise(function(resolve) {
    console.log('promise')
    resolve()
}).then(function() {
    console.log('promise resolved')
})

console.log('end')
Copy the code

Based on the above conclusions, analyze the implementation process:

  • Set up the execution context, enter the execution stack and start executing the code, printingstart
  • As you go down, setTimeout is encountered, and the callback function is placed in the macro task queue, waiting to execute
  • Moving on, there’s a new Promise whose callback will not be put into another task queue, so it will execute synchronously, printingpromiseBut when resolve is resolved,.then puts its internal callback function into the microtask queue
  • You get to the bottom of the code, print it outend. At this point, the main execution stack is emptied and the microtask queue is searched for executable code
  • The microtask queue was found to contain the code that was put in earlier and printed outpromise resolved, the first loop ends
  • The second loop starts with the macro task and checks if there is any executable code in the macro task queuetimeout

So, the print order is start–> PROMISE –>end–> Promise resolved–>timeout

Here is a simple example that makes it easier to understand. So let’s look at a slightly more complicated one (here we use Chinese characters directly to visually indicate the time of printing, so as not to look laborious) :

console.log('First loop main stack starts')

setTimeout(function() {
    console.log('Second loop starts, first macro task in macro task queue executing')
    new Promise(function(resolve) {
        console.log('The first macro task in the macro task queue continues to execute')
        resolve()
    }).then(function() {
        console.log('Microtask execution for second loop microtask queue')
    })
}, 0)

new Promise(function(resolve) {
    console.log('First loop main stack in progress... ')
    resolve()
}).then(function() {
    console.log('First loop microtask, end of first loop')
    setTimeout(function() {
        console.log('Second macro task execution of second loop macro task queue')
    })
})

console.log('First loop main stack completes')
Copy the code

Also, let’s analyze the execution process:

  • First cycle

    1. Go to the execution stack to execute the code, printThe first time the loop starts the main stack
    2. When setTimeout is encountered, the callback is placed in the macro task queue for execution
    3. The promise states that the process is synchronous, printingFirst loop main stack in progress...Then, the callback is put into the microtask queue
    4. printThe first loop main stack completes
    5. To check whether the microtask queue has executable code, there is a third step to put in the task, printFirst loop microtask, end of first loop, the first time the loop ends, setTimeout is encountered, and the callback is put into the macro task queue
  • Second cycle

    1. Start from macro task, check macro task queue, found that there are two macro tasks, respectively is the first cycle of the second step and the first cycle of the fifth step was put into the task, first execute the first macro task, printThe second loop starts, and the first macro task in the macro task queue is executing
    2. If you encounter a promise statement, print itThe first macro task in the macro task queue continuesResolve is resolved, and the. Then callback is added to the microtask queue, where the first task in the macro task queue has not finished executing
    3. When the synchronization code in the first macro task is finished, check the microtask queue and find a piece of code that was put in the second step to printSecond loop microtask execution of the microtask queue, the first macro task is completed
    4. Start performing the second macro task, printingThe second macro task of the second loop macro task queue executesAll task queues are cleared, and the execution is complete

So the printing order is:

  • The first time the loop starts the main stack
  • First loop main stack in progress…
  • The first loop main stack completes
  • First loop microtask, end of first loop
  • The second loop starts, and the first macro task in the macro task queue is executing
  • The second loop continues the microtask of the first macro task in the macro task queue
  • Second loop microtask execution of the microtask queue
  • The second macro task of the second loop macro task queue executes

Take a look at the GIF, where the event loop is visible to the naked eye (with tiny time intervals between loops)

conclusion

Js implementation mechanism is often tested in the interview point, is also very around. But I believe that a complete understanding of the event cycle mechanism, careful analysis, the interview encountered such a problem is not a problem at all. As I write this article, I realize that much of what I understood was wrong. If you think there is a mistake, please help to point out.


Welcome to pay attention to my public number: a front-end, share my understanding of the front-end knowledge from time to time