steady
steady

Reputation: 27

Event execution sequence and rendering

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?

Upvotes: 0

Views: 123

Answers (1)

Kaiido
Kaiido

Reputation: 136588

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.

Upvotes: 1

Related Questions