Understanding Event-Loop from Promise Chain

Interview questions

new Promise(resolve => {
  setTimeout(()=>{
    console.log(666);
    new Promise(resolve => {
     resolve();
    })
    .then(() => {console.log(777);})
  })
  resolve();
 })
 .then(() => {
	    new Promise(resolve => {
	      resolve();
	    })
	   .then(() => {console.log(111);})
	   .then(() => {console.log(222);});
 })
 .then(() => {
	   new Promise((resolve) => {
	     resolve()
	   })
	   .then(() => {
		     new Promise((resolve) => {
		       resolve()
		     })
		    .then(() => {console.log(444)})
	  })
	  .then(() => {
	    console.log(555);
	  })
})
.then(() => {
  console.log(333);
})  

answer

111
222
333
444
555
666
777

If you don't get the correct result, it is necessary to continue to look down.

In order to correctly answer the above question, you need a deep understanding of macro tasks, micro tasks, and Event-Loop.



Knowledge points

Macro task

When the browser executes the code, the JS engine classifies most of the code and divides them into these two queues-macrotask and microtask.

Common macro tasks: script (overall code), XHR callback, setTimeout, setInterval, setImmediate (node ​​unique), I/O.

The above description is still a bit jerky, let's use a case to understand it in depth.

app.js

    setTimeout(()=>{ //宏任务2
      console.log(2);
    },0)
    setTimeout(()=>{  //宏任务3
      console.log(3); 
    },0)
    console.log(1);

Execution result: 1 – 2 – 3

  • When the browser starts running app.js, it starts the first macro task (macro task 1, pointing to the overall code of app.js) and starts to execute.
  • When the first timer is encountered during the execution of macro task 1, the browser will start a new thread to run the logic of the timer, and the current js thread will not stop and skip the timer and continue to execute. After the thread is finished running, its callback function is added to the macro task queue to wait, which is macro task 2.
  • And the js thread here encounters the logic of the timer and then starts a thread to run the timer. The js thread skips this section and continues to execute. When the timer thread finishes running, its callback function is added to the macro task queue to wait , Which forms macro task 3, which is ranked behind macro task 2.
  • When the js thread reaches the end and outputs 1, the macro task 1 is over. The browser will go to the macro task queue to find at this moment. The first one is macro task 2, execute output 2. Macro task 2 ends and executes Macro task 3 output 3.

There are many threads in the browser. js is a single thread and it runs in the js engine. Timers and ajax requests are executed in different threads. For example, when the timer thread is executed, it will put the callback function in In the macro task queue. The same is true for ajax requests. It uses a separate thread to initiate an ajax request. After the request is successful, the callback function is placed in the macro task queue. It needs to be particularly pointed out here that the delay function of the ajax request and the timer itself cannot be regarded as Macro tasks, when the ajax request and timer are executed, their callback functions will be placed in the macro task queue for execution.

Micro task

Microtasks are an integral part of macrotasks. Microtasks and macrotasks are inclusive, not side by side. If you want to talk about microtasks, you need to point out which macrotask it belongs to to make sense.

Common macro tasks: process.nextTick (nodejs side), Promise, etc.

app.js

	console.log(1);
	new Promise((resolve)=>{
		resolve();
    }).then(()=>{
		console.log(2)
    })
	console.log(3)

Execution result: 1 – 3 – 2

  • Run the app.js script file to start macro task 1, and the first line of code execution outputs 1.
  • When Promise is encountered, the callback function of then is placed in the micro task queue of macro task 1 and the thread continues to go down.
  • The code runs to the last line and outputs 3. At this time, the synchronization code is executed, and the micro task queue in the current macro task is checked.
  • Run the first then callback function in the micro task queue to output 2. Check the micro task queue again, and no other tasks are found.
  • The micro task queue is executed, and the macro task 1 is executed.

Macro tasks are started by the host environment. Correspondingly, micro tasks are started by the js engine from the code level.

If there is still ambiguity about the relationship between macro tasks and micro tasks, the following will elaborate on it from the perspective of Event-Loop.

Event-Loop

Event-Loop
As you can see from the above figure, the macro tasks form a sequenced queue. Each macro task is divided into a synchronization code and a micro task queue.

  • Assuming that the current thread of js executes macro task 1, the synchronization code in macro task 1 is executed first.
  • If you encounter Promise or process.nextTick, put their callbacks into the micro task queue of the current macro task 1.
  • If you encounter setTimeout, setInterval, etc., another thread will be started to run the corresponding logic, and the js thread will skip this section and continue to execute. After the new thread is executed, a new one will be created behind the queue of the current macro task 1. Macro task and put the timer's callback function into it.
  • After the synchronization code is executed, the micro task queue of macro task 1 will be executed until all tasks in the micro task queue have been executed.
  • All tasks in the microtask queue are executed, and there is no other code in macro task 1, and the current event loop ends. The js thread starts to execute the next macro task until all macro tasks are executed. This constitutes the event loop mechanism as a whole.


extend

Is the dom operation a macro task or a micro task?

 console.log(1);
 document.getElementById("div").style.color = "red";
 console.log(2);

In practice, it is found that when the above code is executed to the third line, the console is output 1and the domstructure has changed. But it should be noted that the domtree update does not mean that the browser has finished rendering, the browser will only execute all in the micro task queue After finishing, the page will be rendered.

The dom operation can neither be regarded as a macro task nor a micro task.It should belong to the category of synchronous code execution.

Is requestAnimationFrame a macro task or a micro task?

setTimeout(() => {
  console.log("11111")
}, 0)
requestAnimationFrame(() => {
   console.log("22222")
})
new Promise(resolve => {
  console.log('promise');
  resolve();
})
.then(() => {console.log('then')})

Execution result: promise – then – 22222 – 11111

Many people will attribute requestAnimationFrame to the macro task, because they find that it will be executed after the micro task queue is completed.

But in fact, requestAnimationFrame is neither a macro task nor a micro task. Its execution timing is within the scope of the current macro task, and executes after the synchronization code and the micro task queue are executed. It still belongs to the scope of the macro task, but It is executed after the micro task queue is executed.

Promise operating mechanism

The wrapper function is synchronous code

 new Promise((resolve)=>{
    console.log(1);
	resolve();
  }).then(()=>{
    console.log(2);
 })

The wrapped function in the new Promise, that is, the code that outputs 1 is executed synchronously. And the function wrapped in then will be loaded into the microtask queue for execution.


If there is no return in the Promise chain

new Promise((resolve)=>{
    console.log(1)
	resolve();
}).then(()=>{
    console.log(2);
}).then(()=>{
    console.log(3);
}).then(()=>{
    console.log(4);
})

Execution result: 1 – 2 – 3 – 4

In normal development, a new Promise is usually returned in the Promise chain to perform asynchronous operations and return the corresponding value. As follows.

new Promise((resolve)=>{
    console.log(1)
	resolve();
}).then(()=>{
     return new Promise((resolve)=>{
       resolve(2)
     })
}).then((n)=>{
    console.log(n);
})

Execution result: 1 – 2

But in the above code, nothing is returned in the callback of the then function. However, the subsequent callback functions contained in the then will still be executed in turn, returning 1 – 2 – 3 – 4. And it can be connected to the then function indefinitely at the end, and these functions will also be Execute sequentially.


Multiple then function execution order

new Promise((resolve)=>{   // 1
    console.log("a")         // 2         
	resolve();                // 3
}).then(()=>{               // 4
    console.log("b");       // 5
}).then(()=>{               // 6
    console.log("c");       // 7
})                          // 8
console.log("d")          // 9

Execution result: a – d – b – c

  • The 1,2,3 behaviors are synchronously executed code, and output a in one go.
  • At this time, the thread goes to the fourth line and encounters the callback of the then function, and puts it into the queue of the micro task to wait.
  • The thread continues to go back and jumps directly to line 9 and outputs d. Why does it ignore the then on line 6 and jump to line 9? Because the then function callback on line 4 will start to execute after the execution of line 6 Code. (If you don't understand why the 6th line of code is ignored at this moment, you can check the concept of function currying).
  • After the synchronization code is executed, the micro task queue starts to be executed. At this time, the micro task queue contains only a then callback function, and the execution output b.
  • After the execution of lines 4 and 5 is completed, the execution of the 6th line of code begins. The then function callback is found, and it is placed in the micro task queue. At this time, the first micro task is executed, and it is cleared.
  • There is also a micro task that has just been put in the micro task queue, and the execution output c. Clear this micro task, so far the micro task queue is empty, and all tasks have been executed.


Problem solving

With the above knowledge reserve and then return to the original interview question of this article, this question can be easily solved. (For the convenience of elaboration, add the line number on the right)

new Promise(resolve => {            // 1
  setTimeout(()=>{                       // 2
      console.log(666);                   // 3
      new Promise(resolve => {     // 4
        resolve();                              
      })                                          
      .then(() => {console.log(777);})   // 7
  })                                               
  resolve();                                 // 9
 })                                           // 10
 .then(() => {                                // 11
	     new Promise(resolve => {        // 12
	       resolve();                              // 13
	     })
	     .then(() => {console.log(111);})    // 15
	     .then(() => {console.log(222);});   // 16
 })                                                 // 17
 .then(() => {                               // 18
	     new Promise((resolve) => {       // 19
	       resolve()
	     })
	    .then(() => {                               // 22
		     new Promise((resolve) => {        // 23
		       resolve()
		     })
		    .then(() => {console.log(444)})       // 26
	     })
	    .then(() => {                                   // 28
	       console.log(555);                   // 29
	    })
})
.then(() => {                       // 32
  console.log(333);
})  
  • The thread executes the first line of code and synchronously executes the function wrapped by the Promise.
  • Find the timer in the second line, start a macro task, put the timer's callback into the macro task queue to wait, and the thread jumps directly to the 9th line for execution
  • After executing the 9th line, the 11th line of code is executed and the then function is found and placed in the current microtask queue. The thread has no more executable code in the future, so it starts to execute the microtask queue.
  • The execution microtask queue enters the 12th line of code, and when it runs to the 15th line of code, it is found that the then function is placed in the microtask queue to wait. Then the thread jumps directly to the 18th line, and the then function is placed in the microqueue. There is no subsequent execution The first task of the micro task queue, which is the 15th line of code, outputs 111.
  • After line 15 is executed, line 16 is executed and the then callback is put into the micro task queue to wait. Then the thread jumps to the micro task of line 18 to start execution, and executes until line 22 encounters the then function and puts it in the micro task queue to wait. At this time The thread continues to jump down to line 32 when it encounters the then function and puts it into the micro task queue to wait. There is no executable code in the following, and then starts to execute the first task in the micro task queue.
  • The thread jumps to line 16 to execute the microtask and outputs 222, and then jumps to line 22 to execute the next microtask. At line 26, it encounters the then function and puts it in the microtask queue to wait. The thread continues to execute the next microtask and jumps to line 32 Output 333. So far, the three microtasks in this round have all been executed and emptied, and the first task of the microtask queue is executed again. The thread jumps to line 26 and outputs 444.
  • When the thread executes on line 28, it encounters the then function callback and puts it into the micro task queue to wait. There is no executable code in the follow-up, and then starts to execute the first task of the micro task queue, which is 29 lines of code, output 555.
  • After all micro tasks are executed, the current macro task ends. The thread starts to execute the next macro task, and the thread jumps to the third line and outputs 666.
  • After the thread continues to run on the 7th line, it encounters the then callback and puts it into the micro task queue. There is no executable code in the follow-up, and then starts to execute the first task of the micro task queue and outputs 777. The second macro task is executed.

In summary: the output is 111 – 222 – 333 – 444 – 555 – 666 – 777 respectively

Guess you like

Origin blog.csdn.net/brokenkay/article/details/110261000