Search code examples
javascriptevent-loophtml-rendering

Any example proving microtask is executed before rendering?


I saw several articles says that render steps are after microtasks.

I test it with this code:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>
  <p>test</p>
  <script>
const p = document.querySelector('p');
p.textContent = 'text';
Promise.resolve().then(function microtask() {
  debugger;
  p.textContent = 'Promise';
});
  </script>
</body>
</html>

This code seems show that the UI is re-rendered before the microtask run.

Am i think about it wrong? Is there any good example shows that rendering is after microtask run ?


Solution

  • You can simply prove it by looking at the event-loop processing model. To paraphrase its current status while omitting the few steps we're not interested in:

      1. Choose a task to execute
      1. Execute that task
      1. Perform microtask end-point
      1. Update the rendering (if it's time to do so)
      1. Repeat

    So here it's quite clear microtasks are executed before the rendering happens.

    Not yet convinced?

    Here is a snippet which will block your UI for 5s using only microtasks. The page content will not be rendered before this lock is released:

    // We wrap our code in a 0 timeout and two rAF levels
    // to be sure the initial rendering of the page did occur
    setTimeout( () => {
    requestAnimationFrame( () => {
      requestAnimationFrame( () => {
    
        // Now we modify the DOM
        document.querySelector( 'p' ).textContent = "modified";
        // and we start locking the UI using a Promise chain
        const start = performance.now();
        (function lockUI() {     // IIFE
          if( performance.now() - start  < 5000 ) {
            // recursive Promise chaining
            // all these promises will get executed before the next rendering
            // and block the UI rendering
            // even though we were in an rAF callback!
            return Promise.resolve()
              .then( lockUI );
          }
        } )();
        
      } );
    } );
    }, 0 );
    <p>Initial (please wait 5s)</p>

    The careful readers will notice that this script is not even queuing microtasks to the 7th step of the event loop, but to the 11.12 interleaved microtask checkpoint.

    This only better cements the point that the actual rendering is only done at step 11.15, and that anything before can actually delay it.


    So in your test case, "text" should never be rendered, since by calling Promise.resolve().then() you actually queue a microtask, which from the event-loop's point of view is actually the same as a synchronous operation here since there is nothing going on after that queuing.
    Except that there is a case where you could still see the text rendered anyway, which is if the browser enters the spin the event loop algorithm. This may happen if the browser faces a long task and decides it can execute this algorithm, which will allow for the rendering steps to occur even though there is still a long task being ran.
    For instance, Firefox does this whenever you initiate modals like alert() or prompt() etc.

    So in Firefox, this snippet will actually render the text text, Chrome doesn't call this algorithm here, so it won't render anything, not even the initial test:

    const p = document.querySelector('p');
    p.textContent = 'text';
    Promise.resolve().then(function microtask() {
      alert();
      p.textContent = 'Promise';
    });
    <p>test</p>

    However Chrome does call this algorithm with debugger. So this keyword will not block the rendering, and that's why you do see text being rendered.