15 dicembre 2021

Microtasks

I gestori delle Promise .then/.catch/.finally sono sempre asincroni.

Anche quando una Promise è immediatamente risolta, il codice sulle linee sotto .then/.catch/.finally verrà sempre eseguito prima dei gestori.

Ecco una dimostrazione:

let promise = Promise.resolve();

promise.then(() => alert("promise completa"));

alert("codice finito"); // questo alert viene mostrato prima

Se lo esegui, vedrai prima codice finito, in seguito promise done.

Questo è strano, perché la Promise è chiaramente completa dall’inizio.

Perché quindi il .then viene eseguito dopo? Cosa succede?

Coda dei Microtask (Microtasks Queue)

I task asincroni hanno bisogno di una gestione appropriata. Per questo motivo, lo standard specifica una coda interna PromiseJobs, più spesso riferita come “coda dei microtask” (microtask queue) (termine di v8).

Come detto nella specifica:

  • La coda è primo-dentro-primo-fuori: i task messi in coda per primi sono eseguiti per primi.
  • L’esecuzione di un task è iniziata solo quando nient’altro è in esecuzione.

Oppure, per dirla in modo semplice, quando una promise è pronta, i suoi gestori .then/catch/finally sono messi nella coda. Non vengono ancora eseguiti. Il motore JavaScript prende un task dalla coda e lo esegue, quando diventa libero dal codice corrente.

Questo è il motivo per cui “codice finito” nell’esempio sopra viene mostrato prima.

I gestori delle promise passano sempre da quella coda interna.

Se c’è una catena con diversi .then/catch/finally, allora ognuno di essi viene eseguito in modo asincrono. Cioè, viene prima messo in coda ed eseguito quando il codice corrente è completo e i gestori messi in coda precedentemente sono finiti.

Che cosa succede se per noi l’ordine è importante? Come possiamo far funzionare code finished dopo promise done?

Facile, basta metterlo in coda con .then:

Promise.resolve()
  .then(() => alert("promise done!"))
  .then(() => alert("code finished"));

Ora l’ordine è come inteso.

Rigetto non gestito (Unhandled rejection)

Ricordi l’evento “unhandledrejection” dal capitolo Gestione degli errori con le promise?

Ora possiamo vedere esattamente come JavaScript viene a conoscenza che c’è stato un respingimento non gestito (unhandled rejection)

“Unhandled rejection” avviene quando un errore di una promise non è gestito alla fine della coda dei microtask

Normalmente, se ci aspettiamo un errore, aggiungiamo .catch alla catena delle promise per gestirlo:

let promise = Promise.reject(new Error("Promise Fallita!"));
promise.catch(err => alert('catturato'));

// non viene eseguito: errore gestito
window.addEventListener('unhandledrejection', event => alert(event.reason));