With regard to asynchronous JavaScript programming, the previous article examined the JavaScript concurrency model, which is based on event loops. I also answered a question on Stackoverflow about the execution order of setTimeout and Promise, so I can share this with more readers and improve the series of articles on asynchronous JavaScript programming.

My personal blog

preface

Let’s take a look at the common front end test:

var p1 = new Promise(function(resolve, reject){
    resolve(1);
})
setTimeout(function(){
  console.log("will be executed at the top of the next Event Loop");
},0)
p1.then(function(value){
  console.log("p1 fulfilled");
})
setTimeout(function(){
  console.log("will be executed at the bottom of the next Event Loop");
},0)
Copy the code

What is the output order of the above example? The answer to this question, which inspired this article, is:

p1 fulfilled
will be executed at the top of the next Event Loop
will be executed at the bottom of the next Event Loop
Copy the code

After reading this article, you should be able to understand the difference between setTimeout and Promise.

Event loop

This article will not repeat the details of the event loop introduced in the article on JavaScript asynchronous programming. Some supplements and summaries will be made:

Executable code

Think about it, how does JavaScript code execute? Is it line by line? Of course not. JavaScript engines parse and execute JavaScript code piece by piece, not line by line. When parsing and executing a block of code, you need to do some upfront work, such as variable/function promotion, and defining variables/functions. The code block in question, commonly referred to as execuable code, usually includes global code, function code, and eval execution code. The first step is to create an execution context.

Execution context stack

Every time the JavaScript engine starts executing the application, it creates a stack of execution contexts (last in, first out) to manage the execution context. When executing a piece of executable code, an execution context is created, pushed, and then pushed back.

function funA() {
    console.log('funA')}function funB() {
    fun3A();
}

function funC() {
    funB();
}

funC();
Copy the code
ECStack.push(<funC> functionContext); Ecstack.push (<funB> functionContext); ecstack.push (<funB> functionContext); // call funA inside funB, push the context ecstack.push (<funA> functionContext); // unstack ecstack.pop (); // unstack ecstack.pop (); // unstack ecstack.pop (); // Javascript continues to execute subsequent codeCopy the code

In addition, all code is executed from the global context, so the bottom of the stack must be the global execution context.

Asynchronous tasks

Reviewing the JavaScript event loop concurrency model, we learned that both setTimeout and Promise invoke asynchronous tasks, which are managed/scheduled through a task queue. So what’s the difference? More on this below.

Task queue

The basic content and mechanism of task queues have been introduced in the previous section. You can choose to view it. This article expands the introduction of task queues. JavaScript manages all asynchronous tasks through task queues, which can be subdivided into MacroTask Queue and MicoTask Queue.

MacroTask Queue

MacroTask Queue includes setTimeout, setInterval, setImmediate, requestAnimationFrame, UI Rendeing, NodeJS ‘I/O, etc.

MicroTask Queue

There are two main types of MicroTask queues:

  1. Independent callback microTask: Such as Promise, its success/failure callback functions are independent of each other;
  2. Compound callback microTask: for exampleObject.observe.MutationObserverAnd in the NodeJsprocess.nextTick, different state callbacks in the same function body;

MacroTask and MicroTask

JavaScript divides asynchronous tasks into macrotasks and microtasks. What is the difference between them?

  1. Execute synchronized code in turn until it is finished;
  2. Check the MacroTask queue. If there are triggered asynchronous tasks, take the first one and call its event handler, then skip to step 3. If there are no asynchronous tasks to be processed, skip to step 3 directly.
  3. Check the MicroTask queue and execute all asynchronous tasks that have been triggered. Execute event handlers in turn until the execution is complete and skip to step 2. If no asynchronous tasks need to be processed, go back to step 2 and execute subsequent steps in turn.
  4. Finally, go back to step 2, continue to check the MacroTask queue, and perform the next steps in turn;
  5. If all asynchronous tasks are completed, the process ends.

The microTask queue is processed after callbacks as long as no other JavaScript is mid-execution, and at the end of each task. As long as no other JavaScript code is executing and at the end of each task, processing of the microTask queue begins.

It should be noted that the task at the end of each task is usually referred to as macroTask. There is a special task called JavaScript Run, which is also a macroTask. Immediately push the JavaScript Run task to the macroTask queue.

review

The introduction of this article is basically over, so what is the output order of the first topic above? A quick explanation:

  1. Start executing the JavaScript script that will taskJavaScript RunPush macroTask queue;
  2. After the resolvePromise is synchronized;
  3. The first setTimeout task is pushed into the macroTask queue
  4. Push the proimse. then task to the microTask queue;
  5. The second setTimeout task is pushed into the macroTask queue.
  6. After executing the code synchronously, exit the first macroTask, i.eJavaScript Run;
  7. Execute clearing microtasks;
  8. Execute the next macroTask;

Finally, let’s review the content again with a topic:

setTimeout(function(){
  console.log("will be executed at the top of the next Event Loop")},0)
var p1 = new Promise(function(resolve, reject){
    setTimeout((a)= > { resolve(1); }, 0);
});
setTimeout(function(){
    console.log("will be executed at the bottom of the next Event Loop")},0)
for (var i = 0; i < 100; i++) {
    (function(j){
        p1.then(function(value){
           console.log("promise then - " + j)
        });
    })(i)
}

Copy the code

What is the code output? Just make sure:

will be executed at the top of the next Event Loop
promise then - 0
promise then - 1
promise then - 2
...
promise then - 99
will be executed at the bottom of the next Event Loop
Copy the code
  1. First, all the code is executed synchronously, during which three setTimeout asynchronous tasks and 100 Promise asynchronous tasks are registered.
  2. Then check the MacroTask queue, take the first MacroTask to expire, and execute the outputwill be executed at the top of the next Event Loop;
  3. Then check the MicroTask queue and find no expired microtasks. Go to Step 4.
  4. Check MacroTask again and execute the second setTimeout handler, resolve Promise;
  5. Then check the MicroTask queue and find that the Promise has been resolved, and its asynchronous handlers can execute, execute, and printpromise then - 0promise then - 99;
  6. Finally, check the MacroTask queue again and execute the outputwill be executed at the bottom of the next Event Loop
  7. Check the two asynchronous task queues alternately until the execution is complete.