简体   繁体   中英

JavaScript task scheduling, Macrotask and Microtasks

From Jake Archibald 's blog

Fiddle (Click on Hey): https://jsfiddle.net/1rpzycLf/

HTML:

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

JS:

// 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);

When running this piece for the inner div the results I get are

click
mutate
click
mutate
promise
promise
timeout
timeout

and I am struggling to see how this is the case. The Execution should be

  1. First Handler (Macrotask)
  2. Process All Microtasks
  3. Second Handler (Macrotask)
  4. Process All Microtasks
  5. SetTimeout (Macrotask)
  6. SetTimeout (Macrotask)

With this in mind, I am expecting the log to output instead:

click
promise
mutate
click
promise
mutate
timeout
timeout

Not sure why promises are being executed only after two of the click's event handler has been processed. The very first promise should ideally execute after the first mutate but we can see that it is clearly not the case. Anyone know why? (Using firefox 54.0)

When you click on the element, you naturally get click outputted first, because you have a click event handler on it, and the log of the word 'click' in the first thing to happen in the click event handler function.

Next up is setTimeout(function() {}, 0); . This pauses JavaScript's execution, and is like a thread/process yield in C. It doesn't execute until later, so we'll come back to that in a bit.

Because you're not actually doing anything with the promise, it resolves instantly, logging out second .

The mutation happens third because the DOM is read top-to-bottom, and you are mutating the data-random attribute directly after the promise resolves.

Finally, now that the DOM has finished being read, the timeout finishes fourth .

The timeout gets logged twice from the inner <div> due to a separate execution context to where it was called from. This can be seen by the fact that console.log(this) inside an onclick does not provide the same context as setTimeout(function() {console.log(this)}, 0); . Due to bubbling in conjunction with the delayed setTimeout , it attempts to fire first from the child <div> , and then also from the parent <div> (which you technically clicked on).

Thus, you end up with:

click
promise
mutate
timeout
timeout

The click , promise and mutate logs will always come one after another, multiplied by the number of elements that you are clicking on simultaneously. The timeout logs will always come last.

 // 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.clear(); // Added for clarity 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); 
 <div class="outer">Outer <div class="inner">Inner</div> </div> 

Note that different browsers process these differently. I'd assume that Chrome (which my answer is based off on) handles this correctly, due to the code logic.

Firefox handles mutations before promises:

click
mutate
promise
promise
timeout
timeout

Edge handles both mutations and timeouts before promises:

click
mutate
timeout
promise
timeout
promise

IE can't handle promises at all, throwing a syntax error.

Hope this helps! :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM