Diferença entre microtask e macrotask dentro de um contexto de loop de eventos

140

Acabei de ler a especificação Promises / A + e me deparei com os termos microtask e macrotask: consulte http://promisesaplus.com/#notes

Eu nunca ouvi falar desses termos antes, e agora estou curioso sobre qual poderia ser a diferença?

Eu já tentei encontrar algumas informações na web, mas tudo o que encontrei foi este post dos arquivos do w3.org (o que não explica a diferença para mim): http://lists.w3.org/Archives /Public/public-nextweb/2013Jul/0018.html

Além disso, encontrei um módulo npm chamado "macrotask": https://www.npmjs.org/package/macrotask Novamente, não está claro qual é exatamente a diferença.

Tudo o que sei é que ele tem algo a ver com o loop de eventos, conforme descrito em https://html.spec.whatwg.org/multipage/webappapis.html#task-queue e https: //html.spec.whatwg .org / multipage / webappapis.html # perform-a-microtask-checkpoint

Sei que teoricamente deveria conseguir extrair as diferenças pessoalmente, dada essa especificação WHATWG. Mas tenho certeza de que outros também poderiam se beneficiar de uma breve explicação dada por um especialista.

NicBright
fonte
Em resumo: várias filas de eventos aninhados. Você pode até implementar um você mesmo:while (task = todo.shift()) task();
Bergi
1
Para alguém que deseja um pouco mais de detalhes: Segredos do JavaScript Ninja, 2ª Edição, CAPÍTULO 13 Eventos de sobrevivência
Ethan

Respostas:

220

Uma solução geral do loop de eventos terá exatamente uma tarefa sendo processada na fila de macrotask (essa fila é chamada simplesmente de fila de tarefas na especificação WHATWG ). Após o término desta macrotask, todas as microtasks disponíveis serão processadas, ou seja, dentro do mesmo ciclo de execução . Enquanto essas microtasks são processadas, elas podem enfileirar ainda mais microtasks, que serão executadas uma a uma, até que a fila de microtask esteja esgotada.

Quais são as consequências práticas disso?

Se uma microtask enfileirar recursivamente outras microtasks, poderá levar muito tempo até que a próxima macrotask seja processada. Isso significa que você pode acabar com uma interface do usuário bloqueada ou com alguma E / S concluída no seu aplicativo.

No entanto, pelo menos no que diz respeito à função process.nextTick do Node.js. (que enfileira microtarefas ), há uma proteção integrada contra esse bloqueio por meio de process.maxTickDepth. Esse valor é definido como um padrão de 1000, reduzindo o processamento adicional de microtasks depois que esse limite é atingido, permitindo que a próxima macrotask seja processada)

Então, quando usar o que?

Basicamente, use microtarefas quando precisar fazer coisas de forma assíncrona de forma síncrona (ou seja, quando você diria executar essa (micro) tarefa no futuro mais imediato ). Caso contrário, atenha-se às macrotasks .

Exemplos

macrotasks: setTimeout , setInterval , setImmediate , requestAnimationFrame , E / S ,
microtasks de renderização de UI : process.nextTick , Promises , queueMicrotask , MutationObserver

NicBright
fonte
4
Embora exista um ponto de verificação de microtask no loop de eventos, não é aí que a maioria dos desenvolvedores encontrará microtasks. Microtasks são processadas quando a pilha JS esvazia. Isso pode acontecer várias vezes em uma tarefa ou mesmo nas etapas de renderização do loop de eventos.
JaffaTheCake 15/02
2
process.maxTickDepthfoi removido há muito tempo atrás: github.com/nodejs/node/blob/...
RidgeA
você também pode usar o método queueMicrotask () para adicionar um novo microtask
ZoomAll
Obrigado @ZoomAll, não sabia queueMicrotask () até agora. Eu o adicionei à resposta, mais os links para todas as coisas ...
NicBright 09/03
requestAnimationFrame (rAF) não apenas gera microtasks. De um modo geral, a chamada rAF cria uma fila separada
ZoomAll
67

Conceitos básicos nas especificações :

  • Um loop de eventos possui uma ou mais filas de tarefas (a fila de tarefas é a fila de macrotask)
  • Cada loop de eventos possui uma fila de microtask.
  • fila de tarefas = fila de macrotask! = fila de microtask
  • uma tarefa pode ser enviada para a fila de macrotask ou fila de microtask
  • quando uma tarefa é empurrada para uma fila (micro / macro), queremos dizer que a preparação do trabalho está concluída, para que a tarefa possa ser executada agora.

E o modelo de processo do loop de eventos é o seguinte:

Quando a pilha de chamadas estiver vazia, execute as etapas

  1. selecione a tarefa mais antiga (tarefa A) nas filas de tarefas
  2. se a tarefa A for nula (significa que as filas de tarefas estão vazias), vá para a etapa 6
  3. defina "tarefa em execução no momento" para "tarefa A"
  4. execute a "tarefa A" (significa executar a função de retorno de chamada)
  5. defina "tarefa atualmente em execução" como nula, remova "tarefa A"
  6. executar fila de microtask
    • (a). selecione a tarefa mais antiga (tarefa x) na fila de microtask
    • (b) .se a tarefa x for nula (significa que as filas de microtask estão vazias), vá para a etapa (g)
    • (c) .set "tarefa em execução no momento" para "tarefa x"
    • (d) .run "task x"
    • (e) .set "task em execução no momento" para null, remova "task x"
    • (f). selecione a próxima tarefa mais antiga na fila de microtask, pule para a etapa (b)
    • (g) fila de microtask;
  7. pule para a etapa 1.

Um modelo de processo simplificado é o seguinte:

  1. execute a tarefa mais antiga na fila de macrotask e remova-a.
  2. execute todas as tarefas disponíveis na fila de microtask e remova-as.
  3. próxima rodada: execute a próxima tarefa na fila de macrotask (pule a etapa 2)

algo para lembrar:

  1. quando uma tarefa (na fila de macrotask) está em execução, novos eventos podem ser registrados.Então, novas tarefas podem ser criadas.A seguir estão duas novas tarefas criadas:
    • O retorno de chamada de promessaA.then () é uma tarefa
      • A promessaA é resolvida / rejeitada: a tarefa será enviada para a fila de microtask na rodada atual do loop de eventos.
      • A promessaA está pendente: a tarefa será enviada para a fila de microtask na próxima rodada do loop de eventos (pode ser a próxima rodada)
    • O retorno de chamada de setTimeout (retorno de chamada, n) é uma tarefa e será enviado para a fila de macrotask, mesmo n é 0;
  2. a tarefa na fila de microtask será executada na rodada atual, enquanto a tarefa na fila de macrotask precisará aguardar a próxima rodada do loop de eventos.
  3. todos sabemos que a chamada de retorno "click", "scroll", "ajax", "setTimeout" ... são tarefas, no entanto, também devemos lembrar que os códigos js como um todo na tag de script também são uma tarefa (uma macrotask).
wengeezhang
fonte
2
Esta é uma ótima explicação! Obrigado por compartilhar! Mais uma coisa a mencionar está nos NodeJs , setImmediate()é macro / tarefa e process.nextTick()é um micro / trabalho.
LeOn - Han Li
6
E as painttarefas do navegador ? Em qual categoria eles se encaixariam?
Legends
Eu acho que eles se encaixam em micro tarefas (como requestAnimationFrame)
Divyanshu Maithani
Aqui está a ordem na qual o loop de eventos da v8 é executado -> Pilha de chamadas || Micro-tarefas || Fila de tarefas || RAF || Renderizar árvore || Layout || Pintar || <Chamadas nativas do SO para desenhar os pixels em uma tela> <----- 1) DOM (novas alterações), CSSOM (novas alterações), árvore de renderização, layout e pintura acontecem após o retorno de chamada requestAnimationFrame conforme os temporizadores do loop de eventos. É por isso que é importante concluir suas operações DOM antes do rAF, tanto quanto possível, o resto pode ir no rAF. PS: chamar rAF acionará uma execução de macro-tarefa.
Anvesh Checka
Não sei se estou enganado, mas meio que não estou de acordo com esta resposta, as microtasks são executadas antes da macrotask. codepen.io/walox/pen/yLYjNRq ?
walox
9

Acho que não podemos discutir o loop de eventos na separação da pilha, então:

JS tem três "pilhas":

  • pilha padrão para todas as chamadas síncronas (uma função chama outra etc.)
  • fila de microtask (ou fila de trabalhos ou pilha de microtask) para todas as operações assíncronas com prioridade mais alta (process.nextTick, Promises, Object.observe, MutationObserver)
  • fila de macrotask (ou fila de eventos, fila de tarefas, fila de macrotask) para todas as operações assíncronas com prioridade mais baixa (setTimeout, setInterval, setImmediate, requestAnimationFrame, E / S, renderização da interface do usuário)
|=======|
| macro |
| [...] |
|       |
|=======|
| micro |
| [...] |
|       |
|=======|
| stack |
| [...] |
|       |
|=======|

E o loop de eventos funciona desta maneira:

  • execute tudo de baixo para cima da pilha e SOMENTE quando a pilha estiver vazia, verifique o que está acontecendo nas filas acima
  • verifique a micro pilha e execute tudo o que estiver lá (se necessário) com a ajuda da pilha, uma micro tarefa após a outra até que a fila de microtask esteja vazia ou não exija nenhuma execução e SOMENTE verifique a pilha de macro
  • verifique a pilha de macros e execute tudo o que estiver lá (se necessário) com a ajuda da pilha

A pilha do Mico não será tocada se a pilha não estiver vazia. A pilha de macros não será tocada se a micro pilha não estiver vazia OU não exigir execução.

Resumindo: a fila de microtask é quase a mesma da fila de macrotask, mas essas tarefas (process.nextTick, Promises, Object.observe, MutationObserver) têm prioridade mais alta que as macrotasks.

Micro é como macro, mas com maior prioridade.

Aqui você tem um código "definitivo" para entender tudo.

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
fonte
1
Chamar uma fila de uma pilha é totalmente confuso.
Bergi 16/06