Différence entre microtask et macrotask dans un contexte de boucle d'événements

140

Je viens de terminer la lecture de la spécification Promises / A + et je suis tombé sur les termes microtask et macrotask: voir http://promisesaplus.com/#notes

Je n'ai jamais entendu parler de ces termes auparavant, et maintenant je suis curieux de savoir quelle pourrait être la différence?

J'ai déjà essayé de trouver des informations sur le web, mais tout ce que j'ai trouvé est ce post des archives de w3.org (qui ne m'explique pas la différence): http://lists.w3.org/Archives /Public/public-nextweb/2013Jul/0018.html

De plus, j'ai trouvé un module npm appelé "macrotask": https://www.npmjs.org/package/macrotask Encore une fois, il n'est pas précisé quelle est exactement la différence.

Tout ce que je sais, c'est que cela a quelque chose à voir avec la boucle d'événements, comme décrit dans https://html.spec.whatwg.org/multipage/webappapis.html#task-queue et https: //html.spec.whatwg .org / multipage / webappapis.html # perform-a-microtask-checkpoint

Je sais que je devrais théoriquement être capable d'extraire moi-même les différences, étant donné cette spécification WHATWG. Mais je suis sûr que d'autres pourraient également bénéficier d'une brève explication donnée par un expert.

NicBright
la source
En bref: plusieurs files d'attente d'événements imbriquées. Vous pouvez même en implémenter un vous-même:while (task = todo.shift()) task();
Bergi
1
Pour quelqu'un qui veut un peu plus de détails: Secrets of the JavaScript Ninja, 2nd Edition, CHAPITRE 13 Surviving events
Ethan

Réponses:

220

Une remise des gaz de la boucle d'événements aura exactement une tâche en cours de traitement à partir de la file d'attente macrotask (cette file d'attente est simplement appelée la file d'attente des tâches dans la spécification WHATWG ). Une fois ce macrotask terminé, toutes les microtâches disponibles seront traitées, à savoir dans le même cycle de . Pendant que ces microtâches sont traitées, elles peuvent mettre en file d'attente encore plus de microtâches, qui seront toutes exécutées une par une, jusqu'à ce que la file d'attente de microtâches soit épuisée.

Quelles en sont les conséquences pratiques?

Si une microtâche file d'attente de manière récursive d'autres microtâches, le traitement de la prochaine macrotask peut prendre un certain temps. Cela signifie que vous pourriez vous retrouver avec une interface utilisateur bloquée ou une certaine inactivité des E / S dans votre application.

Cependant, au moins en ce qui concerne la fonction process.nextTick de Node.js (qui met en file d'attente les microtâches ), il existe une protection intégrée contre un tel blocage au moyen de process.maxTickDepth. Cette valeur est définie par défaut sur 1000, ce qui réduit le traitement ultérieur des microtâches une fois cette limite atteinte, ce qui permet de traiter la prochaine macrotask )

Alors, quand utiliser quoi?

Fondamentalement, utilisez des microtâches lorsque vous avez besoin de faire des choses de manière asynchrone de manière synchrone (c'est-à-dire lorsque vous diriez effectuer cette (micro-) tâche dans un avenir le plus immédiat ). Sinon, tenez-vous-en aux macrotasks .

Exemples

macrotasks: setTimeout , setInterval , setImmediate , requestAnimationFrame , I / O ,
microtâches de rendu de l'interface utilisateur: process.nextTick , Promises , queueMicrotask , MutationObserver

NicBright
la source
4
Bien qu'il y ait un point de contrôle de microtâche dans la boucle d'événements, ce n'est pas là que la plupart des développeurs rencontreront des microtâches. Les microtasques sont traités lorsque la pile JS se vide. Cela peut se produire plusieurs fois dans une tâche, ou même dans les étapes de rendu de la boucle d'événements.
JaffaTheCake
2
process.maxTickDeptha été supprimé il y a très longtemps: github.com/nodejs/node/blob/…
RidgeA
vous pouvez également utiliser la méthode queueMicrotask () pour ajouter une nouvelle microtâche
ZoomAll
Merci @ZoomAll, je ne connaissais pas queueMicrotask () jusqu'à présent. Je l'ai ajouté à la réponse plus des liens vers toutes les choses ...
NicBright
requestAnimationFrame (rAF) ne génère pas seulement des microtâches. De manière générale, l'appel de la RAF crée une file d'attente distincte
ZoomAll
67

Concepts de base dans les spécifications :

  • Une boucle d'événements a une ou plusieurs files d'attente de tâches (la file d'attente de tâches est une file d'attente de macrotask)
  • Chaque boucle d'événements a une file d'attente de microtâche.
  • file d'attente de tâches = file d'attente macrotask! = file d'attente microtâche
  • une tâche peut être poussée dans la file d'attente de macrotask ou dans la file de microtask
  • lorsqu'une tâche est poussée dans une file d'attente (micro / macro), nous voulons dire que la préparation du travail est terminée, donc la tâche peut être exécutée maintenant.

Et le modèle de processus de boucle d'événements est le suivant:

lorsque la pile d'appels est vide, suivez les étapes

  1. sélectionner la tâche la plus ancienne (tâche A) dans les files d'attente de tâches
  2. si la tâche A est nulle (signifie que les files d'attente de tâches sont vides), passez à l'étape 6
  3. définir "tâche en cours d'exécution" sur "tâche A"
  4. exécuter "tâche A" (signifie exécuter la fonction de rappel)
  5. définir "tâche en cours d'exécution" sur null, supprimer "tâche A"
  6. exécuter la file d'attente de microtâche
    • (a) .sélectionnez la tâche la plus ancienne (tâche x) dans la file d'attente des microtaches
    • (b) .si la tâche x est nulle (signifie que les files d'attente de microtâche sont vides), passez à l'étape (g)
    • (c) .set "tâche en cours d'exécution" sur "tâche x"
    • (d) .run "tâche x"
    • (e). définir "tâche en cours d'exécution" sur null, supprimer "tâche x"
    • (f) .sélectionnez la tâche la plus ancienne suivante dans la file d'attente de microtâche, passez à l'étape (b)
    • (g). terminer la file d'attente de microtâche;
  7. passez à l'étape 1.

un modèle de processus simplifié est le suivant:

  1. exécutez la tâche la plus ancienne de la file d'attente macrotask, puis supprimez-la.
  2. exécutez toutes les tâches disponibles dans la file d'attente de microtâche, puis supprimez-les.
  3. tour suivant: exécuter la tâche suivante dans la file d'attente de macrotask (sauter l'étape 2)

Quelque chose dont il faut se souvenir:

  1. lorsqu'une tâche (dans la file d'attente de macrotask) est en cours d'exécution, de nouveaux événements peuvent être enregistrés.Ainsi, de nouvelles tâches peuvent être créées.Voici deux nouvelles tâches créées:
    • Le rappel de promiseA.then () est une tâche
      • promiseA est résolu / rejeté: la tâche sera poussée dans la file d'attente des microtaches dans le cycle actuel de la boucle d'événements.
      • promiseA est en attente: la tâche sera poussée dans la file d'attente des microtaches lors du prochain tour de la boucle d'événements (peut-être le prochain tour)
    • Le rappel de setTimeout (callback, n) est une tâche, et sera poussé dans la file d'attente de macrotask, même n vaut 0;
  2. La tâche dans la file d'attente de microtâche sera exécutée dans le tour en cours, tandis que la tâche dans la file d'attente de macrotask doit attendre le prochain tour de boucle d'événement.
  3. nous savons tous que le rappel de "click", "scroll", "ajax", "setTimeout" ... sont des tâches, mais nous devons aussi nous souvenir des codes js dans leur ensemble dans le script tag est une tâche (une macrotask) aussi.
wengéezhang
la source
2
C'est une excellente explication! Merci d'avoir partagé!. Une autre chose à mentionner est dans NodeJs , setImmediate()est macro / tâche et process.nextTick()est un micro / travail.
LeOn - Han Li
6
Qu'en est-il des painttâches du navigateur ? Dans quelle catégorie s'inscriraient-ils?
Legends
Je pense qu'ils s'intégreraient dans des micro-tâches (comme requestAnimationFrame)
Divyanshu Maithani
Voici l'ordre dans lequel la boucle d'événements v8 s'exécute -> Call Stack || Micro tâches || File d'attente des tâches || rAF || Arbre de rendu || Disposition || Peinture || <Les appels natifs du système d'exploitation pour dessiner les pixels sur un écran> <----- 1) DOM (nouveaux changements), CSSOM (nouveaux changements), l'arbre de rendu, la mise en page et la peinture se produisent après le rappel requestAnimationFrame selon les minuteries de la boucle d'événements. C'est pourquoi il est important de terminer vos opérations DOM avant rAF autant que vous le pouvez, le repos peut aller dans rAF. PS: l'appel de rAF déclenchera une exécution de macro-tâche.
Anvesh Checka
Je ne sais pas si je me trompe mais je ne suis pas d'accord avec cette réponse, les microtâches passent avant la macrotask. codepen.io/walox/pen/yLYjNRq ?
walox
9

Je pense que nous ne pouvons pas discuter de la boucle d'événements en séparation de la pile, donc:

JS a trois "piles":

  • pile standard pour tous les appels synchrones (une fonction en appelle une autre, etc.)
  • file d'attente de microtâche (ou file d'attente de travail ou pile de microtâche) pour toutes les opérations asynchrones avec une priorité plus élevée (process.nextTick, Promises, Object.observe, MutationObserver)
  • file d'attente macrotask (ou file d'attente d'événements, file d'attente de tâches, file d'attente macrotask) pour toutes les opérations asynchrones avec une priorité inférieure (setTimeout, setInterval, setImmediate, requestAnimationFrame, I / O, rendu de l'interface utilisateur)
|=======|
| macro |
| [...] |
|       |
|=======|
| micro |
| [...] |
|       |
|=======|
| stack |
| [...] |
|       |
|=======|

Et la boucle d'événement fonctionne de cette façon:

  • exécutez tout de bas en haut depuis la pile, et UNIQUEMENT lorsque la pile est vide, vérifiez ce qui se passe dans les files d'attente ci-dessus
  • vérifier la micro-pile et exécuter tout ce qui s'y trouve (si nécessaire) à l'aide de la pile, une micro-tâche après l'autre jusqu'à ce que la file d'attente des microtaches soit vide ou ne nécessite aucune exécution et UNIQUEMENT puis vérifier la pile de macros
  • vérifier la pile de macros et exécuter tout ce qui s'y trouve (si nécessaire) à l'aide de la pile

La pile de Mico ne sera pas touchée si la pile n'est pas vide. La pile de macros ne sera pas touchée si la micro-pile n'est pas vide OU ne nécessite aucune exécution.

Pour résumer: la file d'attente de microtask est presque la même que la file d'attente de macrotask, mais ces tâches (process.nextTick, Promises, Object.observe, MutationObserver) ont une priorité plus élevée que les macrotasks.

Le micro est comme la macro mais avec une priorité plus élevée.

Ici vous avez le code "ultime" pour tout comprendre.

console.log('stack [1]');
setTimeout(() => console.log("macro [2]"), 0);
setTimeout(() => console.log("macro [3]"), 1);

const p = Promise.resolve();
for(let i = 0; i < 3; i++) p.then(() => {
    setTimeout(() => {
        console.log('stack [4]')
        setTimeout(() => console.log("macro [5]"), 0);
        p.then(() => console.log('micro [6]'));
    }, 0);
    console.log("stack [7]");
});

console.log("macro [8]");

/* Result:
stack [1]
macro [8]

stack [7], stack [7], stack [7]

macro [2]
macro [3]

stack [4]
micro [6]
stack [4]
micro [6]
stack [4]
micro [6]

macro [5], macro [5], macro [5]
--------------------
but in node in versions < 11 (older versions) you will get something different


stack [1]
macro [8]

stack [7], stack [7], stack [7]

macro [2]
macro [3]

stack [4], stack [4], stack [4]
micro [6], micro [6], micro [6]

macro [5], macro [5], macro [5]

more info: https://blog.insiderattack.net/new-changes-to-timers-and-microtasks-from-node-v11-0-0-and-above-68d112743eb3
*/
user1660210
la source
1
Appeler une file d'attente une pile est totalement déroutant.
Bergi