In interviews and written tests, there are often questions about the order in which functions such as ‘promise’ and ‘setTimeout’ are mixed. We all know that these asynchronous methods are called after the current task completes, but why is ‘promise’ executed before ‘setTimeout’? What is the specific implementation principle?

For those of you who are struggling for the offer of autumn recruitment like me, welcome to Github to get more of my summary and pits, and make progress together

Question raising

The answers to these questions have been made perfectly clear in the article Tasks, MicroTasks, Queues and Schedules. I suggest that students who can speak English read this article directly instead of reading my “notes”. (They are called notes because most of the text is written, but not translated word by word.)

The following question is a general question that we often see:

console.log('script start');

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

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');
Copy the code

Almost every front-end ER can give the answer without hesitation:

script start
script end
promise1
promise2
setTimeout
Copy the code

The question arises as to why the promise should be executed asynchronously before setTimeout, even when setTimeout is set to zero. Also in Vue, what are microtasks in the nextTick() function principle? Everything is explained in the passage given at the beginning.

Ps: Once again, this article is still my note, the original is much better than I wrote, English can partners strongly recommend reading the original.

Js asynchronous implementation principle

We’ve all heard of event loop at some point. Js is single-threaded, and it becomes very powerful through asynchronism, which is achieved by pushing asynchronous content into tasks and executing callback in tasks after the current task completes.

Tasks is a task queue. When Js executes a synchronous task, it will press the content into Tasks whenever it encounters asynchronous execution and functions. Then, after the current synchronization task is completed, it will execute the corresponding callback in Tasks. For example, when the setTimeout function is encountered, it is always followed by an asynchronous task (callback). In this case, the Tasks queue will be followed by a setTimeout callback in addition to the script currently executing. The callback will not be called until the current synchronization task is complete. That’s why ‘setTimeout’ appears after ‘script end’.

MicroTasks, say something about this. This is different from setTimeout because it is executed immediately after the current Task has completed, or ‘MicroTasks are always executed at the end of the current Task’. Another very important feature is that if the current JS stack is empty (such as when we bind the click event, wait and listen for the click time, the JS stack is empty), it will execute immediately. And I’ll give you an example of that later on, so let’s read on.

So the MicroTasks queue is mainly generated by the callback function of the Promise and mutation Observer

Let’s explain it with a new theory

Okay, so a couple of concepts, so in the first example, what happened?

talk is cheap, show me a animation!! — I said it myself

The following animation illustrates the process:

1. Execute log: script start

  • Tasks: Run script
  • JS stack: script

SetTimeout log: script start

  • Tasks: the Run script | setTimeout callback
  • JS stack: script

3, meet Promise

  • Tasks: Run script | setTimeout callback
  • Microtasks: promise then
  • JS stack: script

4, execute the last line of the log: script start | script end

  • Tasks: Run script | setTimeout callback
  • Microtasks: promise then
  • JS stack: script

4, synchronization, the task has been completed the pop-up corresponding stack log: script start | script end

  • Tasks: Run script | setTimeout callback
  • Microtasks: promise then
  • JS stack:

5, and synchronize tasks is microTasks finally, JS stack pressure into the callback log: script start | script end | promise1

  • Tasks: Run script | setTimeout callback
  • Microtasks: promise then | promise then
  • Returns a new promise, presses the microTasks, and continues executionlog: script start | script end | promise1 | promise2
  • Tasks: Run script | setTimeout callback
  • Microtasks: promise then
  • JS stack: promise2 calback

8, the end of the first Tasks, pop-up log: script start | | script end promise1 | promise2

  • Tasks: setTimeout callback
  • Microtasks:
  • JS stack:

9, the next Tasks log: script start | | script end promise1 | promise2 | setTimeout

  • Tasks: setTimeout callback
  • Microtasks:
  • JS stack: setTimeout callback

Ok, so we’re done, so this is a lot more profound than the idea that “Promise is faster than setTimeout, asynchronously execute the promise first, then execute setTimeout.” Because the callback function that Promise establishes is pressed into the mircroTasks queue, it still belongs to the current Task, whereas setTimeout is equivalent to adding a new Task to the Task sequence

A more complicated example

Ok, with that understanding and foreshadowing, let’s go through a more complex example to familiarize ourselves with a process of JS event processing.

Now there is a page structure like this:

<div class="outer">
  <div class="inner"></div>
</div>

Copy the code

Online instance

// Let's get hold of those elements
var outer = document.querySelector('.outer');
var inner = document.querySelector('.inner');

// Let's listen for attribute changes on the
// outer element
new MutationObserver(function() {
  console.log('mutate');
}).observe(outer, {
  attributes: true
});

// Here's a click listener...
function onClick() {
  console.log('click');

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

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

  outer.setAttribute('data-random'.Math.random());
}

/ /... which we'll attach to both elements
inner.addEventListener('click', onClick);
outer.addEventListener('click', onClick);
Copy the code

Here’s the correct answer. According to the previous theory, the correct answer would be:

click
promise
mutate
click
promise
mutate
timeout
timeout
Copy the code

Of course, different browsers have slightly different implementation of the Event Loop. This is the result printed under Chrome, and the other forms are recommended to read the original text.

Now, why is it in the top order?

The code analysis

According to the conclusion just made:

The Click event is obviously a Task, the Mutation Observer and Promise are in the microTasks queue, and the setTimeout is arranged in the Tasks. so

1. Click event trigger

  • Tasks: Dispatch click
  • Microtasks:
  • JS stack:

2, trigger the click event function, function execution, pressed into the JS stack

  • Tasks: Dispatch click
  • Microtasks:
  • JS stack: onClick
  • Log: ‘click’

3. When setTimeout is encountered, press the Tasks queue

  • Tasks: Dispatch click | setTimeout callBack
  • Microtasks:
  • JS stack: onClick
  • Log: ‘click’

4. Press into Microtasks when you meet promises

  • Tasks: Dispatch click | setTimeout callBack
  • Microtasks: Promise.then
  • JS stack: onClick
  • Log: ‘click’

Outer. setAttribute (mutation

  • Tasks: Dispatch click | setTimeout callBack
  • Microtasks: Promise.then | Mutation observers
  • JS stack: onClick
  • Log: ‘click’

6, the onclick function is executed, the JS stack

  • Tasks: Dispatch click | setTimeout callBack
  • Microtasks: Promise.then | Mutation observers
  • JS stack:
  • Log: ‘click’

7. At this point, JS stack is empty, execute Microtasks

  • Tasks: Dispatch click | setTimeout callBack
  • Microtasks: Promise.then | Mutation observers
  • JS stack: PromiseCallback
  • Log: ‘click’ ‘promise’

8. Microtasks are executed sequentially

  • Tasks: Dispatch click | setTimeout callBack
  • Microtasks: Mutation observers
  • JS stack: Mutation callback
  • Log: ‘click’ ‘promise’ ‘mutate’

When microTasks are empty, is it time to execute the next task (setTimeout)? No, because the bubble in the JS event stream is triggered, the outside Div will also trigger the click function, so let’s go through the same steps again.

The process is omitted, and the result is 9

  • Tasks: Dispatch click | setTimeout callBack | setTmeout callback(outer)
  • Microtasks: Mutation observers
  • JS stack: Mutation callback
  • Log: click promise mutate click promise mutate

10. The first task completes and exits the stack

  • Tasks: setTimeout callBack | setTmeout callback(outer)
  • Microtasks:
  • JS stack: setTimeout callback
  • Log: click promise mutate click promise mutate timeout

11. The second task completes and exits the stack

  • Tasks: setTmeout callback(outer)
  • Microtasks:
  • JS stack: setTimeout(outer) callback
  • Log: click promise mutate click promise mutate timeout timeout

The end of the

So what’s the point here? Is when the MicroTasks are executed: it doesn’t have to be at the end of the Tasks, as long as the JS stack is empty, this rule exit can be executed

If the stack of script Settings objects is now empty, perform a microtask checkpoint — HTML: Cleaning up after a callback step 3

ECMA, on the other hand, explains this

Execution of a Job can be initiated only when there is no running execution context and the execution context stack is The empty… – ECMAScript: Jobs and Job Queues

But for other browsers (Firefox safari IE) the same code, the results are different. The key is, yesjobandmicroTasksThe connection between the two is very vague. But let’s go with Chrome’s implementation.

The final clearance

It’s the same problem as before, except instead of clicking, I’m just going to execute the function

inner.click()
Copy the code

If so, would the result be the same?

The answer is:

click
click
promise
mutate
promise
timeout 
timeout
Copy the code

What!!!!! ?? Why do I feel like I’m learning for nothing? The first big difference is that we added an inner. Click () execution at the bottom of the function, so that the execution of this function is synchronized, so that the start of this task is Run scripts:

1. Unlike mouse clicks, we execute functions and then go inside them

  • Tasks: Run scripts
  • Microtasks:
  • JS stack: script | onClick
  • Log: click

SetTimeout and promise&mutation are encountered

  • Tasks: Run scripts | setTimeout callback
  • Microtasks: Promise.then | Mutation Observers
  • JS stack: script | onClick
  • Log: click

3, The next key, bubble, because we haven’t finished executing the current script, we’re still in the inner. Click () function, so when onclick ends and bubbles start, the script doesn’t end

  • Tasks: Run scripts | setTimeout callback
  • Microtasks: Promise.then | Mutation Observers
  • JS stack: script | onClick (this is the first time that the bubbling click, click is over)
  • Log: click click

4. Repeat the previous content in the bubbling stage

  • Tasks: Run scripts | setTimeout callback |setTimeout callback(outer)
  • Microtasks: Promise.then | Mutation Observers |promise.then
  • JS stack: script | onClick (this is the first time that the bubbling click, click is over)
  • Log: click click

Note that no mutation was added the second time, as there was already one in rendering

Inner. Click () execute Microtasks

  • Tasks: Run scripts | setTimeout callback |setTimeout callback(outer)
  • Microtasks: Promise.then | Mutation Observers |promise.then
  • JS stack:
  • Log: click click promise

6, according to the theory of execution

  • Tasks: Run scripts | setTimeout callback |setTimeout callback(outer)
  • Microtasks: Mutation Observers |promise.then
  • JS stack:
  • Log: click click promise mutate.

I won’t explain the rest, but Microtasks are pushed out of the stack and then executed in sequence.

conclusion

The analysis and depth of this article by Teacher Jake is really admirable. I was also recognized by the interviewer for explaining the Event loop in detail in the interview. So if you have doubts about asynchronous and Event loop, you can thoroughly digest this content and make progress together!