Sistemas y Tecnologías Web: Servidor

Master de II. ULL. 1er cuatrimestre. 2020/2021


Organization ULL-MII-SYTWS-2021   Classroom ULL-MII-SYTWS-2021   Campus Virtual SYTWS   Chat Chat   Profesor Casiano

Table of Contents

Microtasks

Introduction

As with callbacks, promise handlers in .then/.catch/.finally are always asynchronous.

Even when a Promise is immediately resolved, the code on the lines below the .then/.catch/.finally will execute before these handlers.

Asynchronous tasks need proper management:

For that, the ECMA standard specifies an internal queue PromiseJobs, more often referred to as the “microtask queue” (ES8 term).

As stated in the specification:

  • The queue is first-in-first-out: tasks enqueued first are run first.
  • Execution of a task is initiated only when nothing else is running.

Or, to say more simply,

  1. when a promise is ready, its .then/catch/finally handlers are put into the queue; they are not executed yet.
  2. When the JavaScript engine becomes free from the current code, it takes a task from the queue and executes it.

If there’s a chain with multiple .then/catch/finally, then every one of them is executed asynchronously. That is,

  • It first gets queued when is ready, then executed only when
    1. the current code is complete and
    2. previously queued handlers are finished.

For example:

1
2
3
4
Promise.resolve()
  .then(() => console.log("1"))
  .then(() => console.log("2"));
console.log(0);

Will produce as output: "0\n1\n2\n"

Microtasks are used under the cover of await as well, as it’s another form of promise handling.

Question 1

What is the output of this program?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Promises that resolve before the current function ends 
// will be executed right after the current function.
//
let promise = new Promise(function(resolve, reject) {
    resolve(1)
});

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

console.log('a');

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

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

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

console.log('b');

/*
 * The message queue contains all the events waiting to be executed by the browser (scripts, repaint, reflows/layouts, etc.). 
 * They are added to the order they are triggered based on event triggering, timers, user input, DOM manipulation, etc.

  The event loop pulls from the message queue one at a time and
  adds them to the stack for execution.

  The job queue is a new queue that contains then() events added to the queue after the completion of a Promise. 
  In theory the browser can pull from either queue when the event loop occurs. 
  In practice, Promise jobs are given priority and that queue is emptied first. 
  This is because the specification specifies then() to be executed after the current event on the call stack is finished.
*/

Solution 1

Explanations

There is a special function queueMicrotask(func) that queues func for execution in the microtask queue.

The engine executes all tasks from microtask queue, prior to running any other macrotasks or rendering or anything else.

The richer event loop picture may look like this figure taken from the blog ddcode.net

Almost quoting this blog The JavaScript event loop: micro-tasks and macro-tasks by Siddharth Jain (2019):

One macro-task is completed from the macro-task (Task) queue inside message queue. On completion of that task, the event loop goes to the micro-task (Job) queue. The event loop does not look into the next action until the completion of the entire micro-task (Job) queue. It finishes the entire micro-task queue and then moves back to render (if needed) and then to the macro-task queue.

Task Queue → Micro-task → Render → Macro-task

Observe that DOM modification (dynamic changes in the DOM, for example, added or removed elements or changes in attributes values) go to the microtask queue, so that these tasks can be executed before the browser re-renders.

All microtasks are completed before any other event handling or rendering or any other macrotask takes place.

That’s important, as it guarantees that the application environment is basically the same (no mouse coordinate changes, no new network data, etc) between microtasks.

Question 2

What is the output of this program?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
setTimeout(function () {
    console.log('macrotask 1');
}, 0);

Promise.resolve().then(function () {
    console.log('microtask 1');
    setTimeout(function () {
        console.log('macrotask 2');
        Promise.resolve().then(
            () => console.log('Nested microtask 3')
        )
    }, 0);
}).then(function () {
    console.log('microtask 2');
});

Solution 2

Referencias (Read also)

Comment with GitHub Utterances

Comment with Disqus

thread de discusion