0

var con = document.getElementById('con');
con.onclick = function () {
  Promise.resolve().then(function Promise1() {
    con.textContent = 0;
    // requestAnimationFrame(() => con.textContent = 0)
  });
};
<div id="con">this is con</div>

Why this code does not trigger rendering after performing microtasks?

setTimeout(function setTimeout1() {
  console.log('setTimeout1')
}, 0)
var channel = new MessageChannel();
channel.port1.onmessage = function onmessage1() {
  console.log('postMessage');
  Promise.resolve().then(function promise1() {
    console.log('promise1');
  })
};
channel.port2.postMessage(0);
setTimeout(function setTimeout2() {
  console.log('setTimeout2')
}, 0);
console.log('sync');

Why postmessage is executed before timer?

1
  • Please code as text, never as image. And a single question per question too. Commented Jun 17, 2020 at 4:13

1 Answer 1

1

Why this code does not trigger rendering after performing microtasks?

It does, otherwise you wouldn't see the text being updated...

Maybe you are not able to tell it from your dev tools?

This is probably because mouse events are now generally throttled to the screen-refresh rate, meaning that when the task dispatching the mouse event will run, you'd already be in a painting frame, this may be for an other reason (because to my knowledge, mousemove events are throttled this way, not click...).
So there, your Promise callback will get executed synchronously (with only the sixth step "set currentTask to null" in between), before the update the rendering steps kicks in, and all the dev tools will see is a normal painting frame, just like it was expecting.
So maybe, the dev tools won't show anything particular here, but given the broadness of your claim, it's quite hard to pin-point a particular reason, and this is just a theory of mine.

You can try to validate this theory by calling requestAnimationFrame from inside such an event and check if it did execute in the same event loop iteration:

onclick = (evt) => {
  console.clear();
  setTimeout( () => console.log( 'timeout' ), 0 );
  requestAnimationFrame( () => console.log( 'rAF' ) );
};
Click anywhere<br>
If "rAF" gets logged before "timeout", the click event got handled in a painting frame.

For me it does quite often in Chrome, and only once in a while in Firefox, but in the mean time I know Chrome's rAF is broken... so this theory is quite weak.


Why postmessage is executed before timer?

That will depend on the User-Agent (browser) and on when this code is executed for this statement to hold true, and also of course for the reason why it does.

In Chrome, they set a minimum 1ms to the timeout value passed to setTimeout:

  base::TimeDelta interval_milliseconds =
    std::max(base::TimeDelta::FromMilliseconds(1), interval);

the message task has no timeout and will thus get queued immediately. So if no other task is to be processed, it will be the next one executed, long before the 1ms timeout resolves.

In Firefox, they treat tasks scheduled by setTimeout as low priority, when scheduled from the page load (that means that in Firefox, the message task would actually fire after the setTimeout one, if both are scheduled after the page load:

function test() {
  setTimeout(function setTimeout1() {
    console.log('setTimeout1')
  }, 0)
  var channel = new MessageChannel();
  channel.port1.onmessage = function onmessage1() {
    console.log('postMessage');
    Promise.resolve().then(function promise1() {
      console.log('promise1');
    })
  };
  channel.port2.postMessage(0);
  setTimeout(function setTimeout2() {
    console.log('setTimeout2')
  }, 0);
  console.log('sync');
}
console.log( 'testing @ page load' );
test();
setTimeout(() => {
  console.log( 'testing after page load' );
  test();
}, 1000 );

/* results in Firefox:

testing @ page load
sync
postMessage
promise1
setTimeout1
setTimeout2
testing after page load
sync
setTimeout1
setTimeout2
postMessage
promise1
*/
).
So there, in this particular case of a page load, they will treat the message task as more important than the timeout one, and when the task executor will have to choose which task to execute next (as part of the first step of the Event Loop processing model), it will pick the message over the timeout.

But these are implementation quirks, and nothing in the specs does formalize this behavior.

Sign up to request clarification or add additional context in comments.

5 Comments

(console.time('stop') for (let index = 0; index < 10000e3; index++) { } console.timeEnd('stop') ) .When I try to execute this code before the message is instantiated,this time the timer should be in the task quenue. Why is the order the same?
Sorry I'm not following... Are you saying that in Chrome you do something like setTimeout(fn, 0); wait(10s); postMessage() and are expecting setTimeout to win? There is no real reason no, though I guess it's a bit of an undefined behavior, but the message task is still enqueued directly, while the setTimeout one will be after the 1ms resolves, and that will be after the current task ends, which is not before your blocking while loop ends.
Also, remember that as I said in my answer, UAs are allowed to pick whatever task-source they want from the first step of the event loop processing model, Chrome may very well have an higher priority set on the message task-source than on the timers one in addition of the 1ms minimal timeout on setTimeout
Thank you very much for your answer. In addition, I want to know about how chrome implements this. Is there any theoretical knowledge for me to get it, or can I only read the source code?
Yes, read the source, and check their notes. Also I did write an answer recently on the subject for the specs view on the matter, with a note about an incoming game changer that Chrome already has in its source.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.