Você conhece Ivan Tulup? Provavelmente sim, você ainda não sabe que tipo de pessoa é essa e precisa cuidar muito do estado do sistema cardiovascular dele.
Sobre isso e como o assincronismo funciona no JS, como o Event Loop funciona nos navegadores e no Node.js, existem diferenças e talvez coisas semelhantes foram contadas por
Mikhail Bashurov (
SaitoNakamura ) em seu relatório sobre o RIT ++. Temos o prazer de compartilhar com você a transcrição desta apresentação informativa.
Sobre o palestrante: Mikhail Bashurov é um desenvolvedor web fullstack em JS e .NET da Luxoft. Ele adora uma bela interface do usuário, testes ecológicos, transpilação, compilação, compilador que permite a técnica e melhora a experiência do desenvolvedor.
Nota do editor: o relatório de Mikhail foi acompanhado não apenas por slides, mas por um projeto de demonstração no qual você pode clicar nos botões e assistir de forma independente à execução de embaralhamento. A melhor opção seria abrir a
apresentação em uma guia adjacente e consultá-la periodicamente, mas o texto também fornecerá links para páginas específicas. E agora passamos a palavra ao orador, aproveite a leitura.
Avô Ivan Tulup
Eu tive uma candidatura para Ivan Tulup.

Mas eu decidi seguir um caminho mais conformista, então conheça o avô Ivan Tulup!

De fato, apenas duas coisas precisam ser conhecidas sobre ele:
- Ele gosta de jogar cartas.
- Ele, como todas as pessoas, tem um coração e bate.
Fatos de ataque cardíaco
Você já deve ter ouvido falar que os casos de doenças cardíacas e mortalidade deles recentemente se tornaram mais frequentes. Provavelmente a doença cardíaca mais comum é um ataque cardíaco, ou seja, um ataque cardíaco.
O que é interessante sobre ataque cardíaco?
- Na maioria das vezes, ocorre na segunda-feira de manhã.
- Em pessoas solteiras, o risco de um ataque cardíaco é duas vezes maior. Aqui, talvez, o ponto esteja apenas na correlação, e não em um relacionamento causal. Infelizmente (ou felizmente), no entanto, é assim.
- Dez condutores morreram de ataque cardíaco durante a condução (trabalho aparentemente muito nervoso!).
- Um ataque cardíaco é uma necrose do músculo cardíaco causada pela falta de fluxo sanguíneo.
Temos uma artéria coronária que traz sangue para o músculo (miocárdio). Se o sangue começa a fluir mal para ele, o músculo morre gradualmente. Naturalmente, isso tem um efeito extremamente negativo sobre o coração e seu trabalho.
O avô Ivan Tulup também tem um coração e bate. Mas nosso coração bombeia sangue, e o coração de Ivan Tulup bombeia nosso código e nossas tristezas.
Tasky: um grande círculo de circulação sanguínea
O que são tarefas? O que geralmente pode ser preguiçoso em um navegador? Por que eles são necessários?
Por exemplo, executamos código de um script. Este é um batimento cardíaco e agora temos fluxo sanguíneo. Clicamos no botão e assinamos o evento - o manipulador de eventos desse evento cuspiu - o retorno de chamada que enviamos. Eles definiram Timeout, o retorno de chamada funcionou - outra tarefa. E assim, em partes, um batimento cardíaco é uma tarefa.

Existem muitas fontes diferentes de repolho, de acordo com a especificação, existem muitas. Nosso coração continua batendo e, enquanto bate, está tudo bem conosco.
Loop de eventos no navegador: versão simplificada
Isso pode ser representado em um diagrama muito simples.

- Há uma tarefa, nós a concluímos.
- Em seguida, executamos a renderização do navegador.
Mas, na verdade, isso não é necessário, porque em alguns casos o navegador pode não renderizar entre duas tarefas.
Isso pode acontecer, por exemplo, se o navegador pode decidir agrupar vários tempos limite ou vários eventos de rolagem. Ou, em algum momento, algo dá errado, e o navegador decide, em vez de 60 qps (a taxa de quadros usual para que tudo corra bem e suavemente), para mostrar 30 qps. Assim, ele terá muito mais tempo para executar seu código e outros trabalhos úteis, e poderá executar vários choques.
Portanto, a renderização não é realmente executada após cada tarefa.
Tasky: classificação
Existem dois tipos de operações em potencial:
- Ligação de E / S;
- CPU ligado.
CPU bound é o nosso trabalho útil que fazemos (acredite, mostre, etc.)
Os limites de E / S são os pontos nos quais podemos compartilhar nossas tarefas. Pode ser:
Criamos setTimeout 5000 ms e esperamos apenas esses 5000 ms, mas podemos fazer outro trabalho útil. Somente quando esse tempo passa, recebemos o retorno de chamada e fazemos algum trabalho nele.
Fomos online. Enquanto aguardamos uma resposta da rede, estamos apenas aguardando, mas também podemos fazer algo útil.
Ou, por exemplo, vamos ao Network BD. Também estamos falando sobre o Node.js, inclusive, e se quisermos acessar a rede a partir do Node.js. - esta é a mesma tarefa potencial de E / S vinculada (entrada / saída).
Leia o arquivo - potencialmente não é uma tarefa vinculada à CPU. No Node.js, ele é executado no pool de encadeamentos devido a uma API Linux ligeiramente torta, para ser sincero.
Então CPUbound é:
- Por exemplo, quando fazemos um loop for de / for (;;) ou passamos pela matriz de alguma forma usando métodos adicionais: filtro, mapa etc.
- JSON.parse ou JSON.stringify, ou seja, serialização / desserialização de mensagens. Tudo isso é feito na CPU, não podemos esperar que tudo seja executado magicamente em algum lugar.
- Contagem de hashes, ou seja, por exemplo, mineração de criptografia.
Claro, a criptografia também pode ser extraída na GPU, mas eu acho - GPU, CPU - você entende essa analogia.
Tasky: arritmia e trombo
Como resultado, verifica-se que o nosso coração bate: faz uma tarefa, a segunda, a terceira - até que façamos algo errado. Por exemplo, passamos por uma matriz de 1 milhão de elementos e contamos a soma. Parece que isso não é tão difícil, mas pode levar um tempo tangível. Se tomarmos constantemente um tempo tangível sem liberar tarefas, nossa renderização não poderá ser executada. Ele pairava nesse desejo, e toda a arritmia começa.
Acho que todo mundo entende que a arritmia é uma doença cardíaca bastante desagradável. Mas você ainda pode morar com ele. O que acontece se você colocar uma tarefa que simplesmente trava o Loop de Eventos inteiro em um loop sem fim? Você meio que coloca um coágulo de sangue na coronária ou em alguma outra artéria, e tudo ficará completamente triste. Infelizmente, nosso avô Ivan Tulup vai morrer.
Então, o avô Ivan morreu ...

Para nós, isso significa que a guia inteira congela completamente - você não pode clicar em nada e o Chrome diz: "Ah, Snap!"
Isso é muito pior do que os erros do site quando algo deu errado. Mas se tudo travou, e mesmo, provavelmente, a CPU foi carregada e o usuário geralmente travou, o mais provável é que ele nunca volte ao seu site.
Portanto, a idéia é a seguinte: temos uma tarefa e não precisamos ficar nessa tarefa por muito tempo. Precisamos liberá-lo rapidamente, para que o navegador, se houver, possa renderizar (se quiser). Se você não quiser - ótimo, dance!
Philip Roberts Demo: Lupa por Philip Roberts
Considere
um exemplo :
$.on('button', 'click', function onClick(){ console.log('click'); }); setTimeout(function timeout() { console log("timeout"); }. 5000); console.log(“Hello world");
A essência é a seguinte: temos um botão, assinamos (addEventListener), o Timeout é chamado por 5 segundos e imediatamente no console.log, escrevemos “Olá, mundo!”. Em setTimeout, escrevemos Timeout, em onClick, escrevemos Click.
O que acontecerá se a rodarmos e clicarmos muitas vezes no botão - quando o Timeout será realmente executado? Vamos ver a demonstração:
O código começa a executar, entra na pilha, o Timeout continua. Enquanto isso, clicamos no botão. Na parte inferior da fila, vários eventos foram adicionados. Enquanto o Click está sendo executado, o Tempo limite aguarda, embora cinco segundos tenham se passado.
Aqui, o onClick é rápido, mas se você colocar uma tarefa mais longa, tudo congelará, como já explicado. Este é um exemplo muito simplificado. Aqui está uma vez, mas nos navegadores, de fato, nem tudo é assim.
Em que ordem os eventos são executados - o que a especificação HTML diz?
Ela diz o seguinte: temos 2 conceitos:
- fonte de tarefas;
- fila de tarefas.
A fonte da tarefa é um tipo de tarefa. Pode ser a interação do usuário, ou seja, onClick, onChange - algo com o qual o usuário interage; ou timers, ou seja, setTimeout e setInterval, ou PostMessages; ou mesmo tipos completamente selvagens, como a origem da tarefa Serialização do Canvas Blob - também um tipo separado.
A especificação diz que, para a mesma tarefa, as tarefas de origem terão a garantia de serem executadas na ordem em que forem adicionadas. Para todo o resto, nada é garantido, porque pode haver um número ilimitado de filas de tarefas. O navegador decide quantos haverá. Com a ajuda da fila de tarefas e sua criação, o navegador pode priorizar determinadas tarefas.
Prioridades do navegador e filas de tarefas

Imagine que temos 3 linhas:
- interação do usuário;
- timeouts
- postar mensagens.
O navegador começa a obter tarefas dessas filas:
- Primeiro, ele assume a interação do usuário do Focus - isso é muito importante - um batimento cardíaco se foi.
- Então ele pega postMessages - bem, postMessages é uma prioridade bastante alta, legal!
- O próximo, onChange, também é novamente da interação do usuário em prioridade.
- O próximo onClick é enviado. A fila de interação do usuário terminou, exibimos para o usuário tudo o que é necessário.
- Então pegamos setInterval , adicionamos postMessages.
- setTimeout executará apenas os mais recentes . Ele estava em algum lugar no final da linha.
Este é novamente um exemplo muito simplificado e, infelizmente,
ninguém pode garantir como isso funcionará nos navegadores , porque eles mesmos decidem tudo isso. Você precisa testar isso sozinho se quiser descobrir o que é.
Por exemplo, postMessages tem precedência sobre setTimeout. Você pode ter ouvido falar em setImmediate, que, por exemplo, nos navegadores IE, era apenas nativo. Mas existem polyfiles que são baseados principalmente não no setTimeout, mas na criação de um canal postMessages e na assinatura dele. Isso geralmente funciona mais rápido porque os navegadores a priorizam.
Bem, essas tarefas são realizadas. Em que ponto concluímos nossa tarefa e entendemos que podemos dar o próximo, ou que podemos renderizar?
Stack
A pilha é uma estrutura de dados simples que funciona com o princípio de "último a entrar - primeiro a sair", ou seja, "Coloque o último - você recebe o primeiro
. " A contraparte mais próxima, provavelmente real, é um baralho de cartas. Portanto, nosso avô Ivan Tulup adora jogar cartas.

O exemplo acima, no qual existe algum código, o mesmo exemplo pode ser cutucado na
apresentação . Em algum lugar que chamamos handleClick, digite console.log, chame showPopup e window. confirmar. Vamos formar uma pilha.
- Então, primeiro pegamos o handleClick e enviamos a chamada para essa função para a pilha - ótimo!
- Então nós entramos no corpo dele e o executamos.
- Colocamos console.log na pilha e a executamos imediatamente, porque tudo existe para executá-la.
- Em seguida, colocamos showConfirm - esta é uma chamada de função - ótimo.
- Colocamos funções na pilha - colocamos seu corpo, ou seja, window.confirm.
Não temos mais nada - estamos fazendo isso. Uma janela será exibida: “Você tem certeza?”, Clique em “Sim” e tudo sairá da pilha. Agora terminamos o corpo showConfirm e o corpo handleClick. Nossa pilha é limpa e podemos passar para a próxima tarefa. Pergunta: OK, agora eu sei que você precisa dividir tudo em pedaços pequenos. Como posso, por exemplo, fazer isso no caso mais elementar?
Particionando uma matriz em pedaços e processando-os de forma assíncrona
Vejamos o exemplo mais "testa". Eu o aviso imediatamente: por favor, não tente repetir isso em casa - ele não será compilado.

Temos uma matriz grande e grande e queremos calcular algo com base nela, por exemplo, para analisar alguns dados binários. Podemos simplesmente dividi-lo em pedaços: processar essa peça, isso e isso. Selecionamos o tamanho do pedaço, por exemplo, 10 mil elementos, e consideramos quantos pedaços teremos. Temos uma função parseData que entra no limite da CPU e pode realmente fazer algo pesado. Em seguida, dividimos o array em partes, setTimeout (() => parseData (slice), 0).
Nesse caso, o navegador poderá priorizar novamente a interação do usuário e renderizar no meio. Ou seja, você pelo menos libera seu Loop de Eventos, e ele continua funcionando. Seu coração continua batendo, e isso é bom.
Mas este é realmente um exemplo muito "frontal". Existem muitas APIs nos navegadores para ajudá-lo a fazer isso de uma maneira mais especializada.
Além de setTimeout e setInterval, existem APIs que ultrapassam os limites, como, por exemplo, requestAnimationFrame e requestIdleCallback.
Provavelmente muitos estão familiarizados com
requestAnimationFrame e até já o usam. É executado antes da renderização. Seu charme é que, em primeiro lugar, ele tenta executar a cada 60 fps (ou 30 fps) e, em segundo lugar, tudo isso é feito imediatamente antes da criação do CSS Object Model, etc.

Portanto, mesmo se você tiver vários requestAnimationFrame, eles realmente agruparão todas as alterações e o quadro será concluído. No caso de setTimeout, você certamente não pode obter essa garantia. Um setTimeout mudará uma coisa, a outra outra, e entre a renderização poderá escorregar - você terá uma sacudida na tela ou outra coisa. RequestAnimationFrame é ótimo para isso.
Além disso, também há
requestIdleCallback. Talvez você tenha ouvido falar que ele é usado no React v16.0 (Fiber). O RequestIdleCallback funciona de tal forma que, se o navegador entender que há tempo entre os quadros (60 fps) para fazer algo útil e, ao mesmo tempo, eles já fizeram tudo - eles executaram a tarefa, requestAnimationFrame - parece legal, então pode produzir pequenos quanta, digamos, 50 ms cada, para que você possa fazer alguma coisa (modo IDLE).
Não está no diagrama acima, porque não está localizado em nenhum local específico. O navegador pode decidir colocá-lo antes do quadro, após o quadro, entre o requestAnimationFrame e a renderização, após a tarefa, antes da tarefa. Ninguém pode garantir isso.
É garantido a você que, se você tiver um trabalho que não esteja relacionado à alteração do DOM (porque requestAnimationFrame é animação e assim por diante), embora não seja uma super prioridade, mas tangível, requestIdleCallback é a sua saída.
Portanto, se tivermos uma operação longa vinculada à CPU, podemos tentar dividi-la em pedaços.
- Se for uma alteração do DOM, use requestAnimationFrame.
- Se esta for uma tarefa não prioritária, de curta duração e não difícil, que não sobrecarregará a CPU, requestIdleCallback.
- Se tivermos uma tarefa grande e poderosa que precisa ser executada constantemente, vamos além do Event Loop e usaremos os WebWorkers. Não há outro caminho.
Tarefas nos navegadores:- Esmague tudo em pequenas tarefas.
- Existem muitos tipos de tarefas.
- As tarefas são priorizadas por esses tipos através de filas de especificação.
- Muito é decidido pelos navegadores, e a única maneira de entender como funciona é simplesmente verificar se um ou outro código está em execução.
- Mas a especificação nem sempre é respeitada!
O problema é que nosso Ivan Tulup é um avô antigo, porque as implementações do Event Loop nos navegadores também são realmente muito antigas. Eles foram criados antes da especificação ser escrita, portanto, infelizmente, a especificação é respeitada na medida em que. Mesmo se você ler como deve ser a especificação, ninguém garante que todos os navegadores sejam compatíveis. Portanto, verifique nos navegadores como isso realmente funciona.
O avô Ivan Tulup nos navegadores é uma pessoa pouco previsível, com alguns recursos interessantes, você precisa se lembrar disso.
Terminator Santa: laço de mascote no Node.js
O Node.js é mais parecido com alguém assim.

Porque, por um lado, é o mesmo avô com barba, mas ao mesmo tempo tudo é distribuído em fases e é claramente pintado onde é feito.
Fases do loop de eventos no Node.js:- temporizadores;
- retorno de chamada pendente;
- ocioso, prepare-se;
- sondagem;
- cheque;
- fechar retornos de chamada.
Tudo, exceto o último, não está muito claro do que isso significa. As fases têm nomes tão estranhos, porque sob o capô, como já sabemos, temos Libuv para governar todos:
- Linux - epoll / POSIX AIO;
- BSD - kqueue;
- Windows - IOCP;
- Solaris - portas de eventos.
Milhares de todos eles!
Além disso, o Libuv também fornece o mesmo loop de eventos. Ele não possui as especificidades do Node.js, mas existem fases, e o Node.js apenas as utiliza. Mas, por algum motivo, ela pegou os nomes de lá.
Vamos ver o que cada fase realmente significa.
A fase Timers executa:
- Temporizadores prontos para retorno de chamada;
- setTimeout e setInterval;
- Mas NÃO setImmediate é uma fase diferente.
Fase de retorno de chamada pendente
Antes disso, a fase de documentação denominava retornos de chamada de E / S. Mais recentemente, essa documentação foi corrigida e deixou de se contradizer. Antes disso, em um local estava escrito que os retornos de chamada de E / S eram executados nesta fase, em outra - na fase de pesquisa. Mas agora tudo está escrito lá inequivocamente e bem, então leia a documentação - algo se tornará muito mais compreensível.
Na fase de retorno de chamada pendente, retornos de chamada de algumas operações do sistema (erro TCP) são executados. Ou seja, se no Unix houver um erro no soquete TCP, nesse caso, ele não deseja descartá-lo imediatamente, mas no retorno de chamada, que será executado exatamente nesta fase. É tudo o que precisamos saber sobre ela. Praticamente não estamos interessados nisso.
Fase Inativa, preparar
Nesta fase, não podemos fazer nada, por isso vamos esquecer isso em princípio.

Fase de votação
Esta é a fase mais interessante do Node.js, porque realiza o principal trabalho útil:
- Executa retornos de chamada de E / S (sem fase de retorno de chamada pendente!).
- Aguardando eventos de E / S;
- É legal fazer setImmediate;
- Sem temporizadores;
No futuro, o setImmediate será executado na próxima fase de verificação, isto é, garantida antes dos temporizadores.
E também a fase de pesquisa controla o fluxo do loop de eventos. Por exemplo, se não tivermos temporizadores, não haverá setImmediate, ou seja, ninguém ligou o timer, setImmediate, apenas bloqueamos nesta fase e aguardamos o evento de E / S, se algo acontecer conosco, se houver algum retorno de chamada se nos inscrevemos em alguma coisa.
Como é implementado um modelo sem bloqueio? Por exemplo, no mesmo Epoll, podemos assinar um evento - abrir um soquete e aguardar que algo seja escrito nele. Além disso, o segundo argumento é o tempo limite, ou seja, aguardaremos o Epoll, mas se o tempo limite terminar e o evento de E / S não chegar, o tempo limite será encerrado. Se um evento vier da rede (alguém escreve no soquete), ele virá.
Portanto, a fase de pesquisa recupera o primeiro retorno de chamada do heap (o heap é uma estrutura de dados que permite entrega e entrega bem classificadas), gasta o tempo limite, grava esse tempo limite e libera tudo. Assim, mesmo que ninguém nos escreva no soquete, o tempo limite funcionará, retornará à fase de pesquisa e o trabalho continuará.
É importante observar que, na fase de pesquisa, há um limite no número de retornos de chamada por vez.
É triste que nas demais fases não seja. Se você adicionar 10 bilhões de tempo limite, você adicionará 10 bilhões de tempo limite. Portanto, a próxima fase é a fase de verificação.
Fase de verificação
É aqui que o setImmediate é executado. A fase é bonita nesse setImmediate, chamado na fase de pesquisa, é garantido para executar mais cedo que o timer. Porque o cronômetro só estará no próximo tick no início e antes da fase de pesquisa. Portanto, não podemos ter medo de competir com outros cronômetros e usar essa fase para coisas que não queremos, por algum motivo, executar em um retorno de chamada.
Retorno de chamada de fase
Essa fase não executa todos os nossos retornos de chamada de fechamento de soquete e outros tipos:
socket.on('close', …).
Ela os executa apenas se esse evento ocorreu inesperadamente, por exemplo, alguém do outro lado enviou: "Tudo - feche a tomada - vá daqui, Vasya!" Então essa fase funcionará, porque o evento é inesperado. Mas isso não nos afeta particularmente.
Processamento assíncrono incorreto de chunks no Node.js
O que acontecerá se colocarmos o mesmo padrão que adotamos nos navegadores com setTimeout no Node.js - isto é, dividiremos a matriz em partes, para cada parte que fizermos setTimeout - 0.
const bigArray = [1..1_000_000] const chunks = getChunks(bigArray) const parseData = (slice) =>
Você acha que há algum problema com isso?
Eu já corri um pouco à frente quando disse que se você adicionar 10 mil tempos limite (ou 10 bilhões!), Haverá 10 mil temporizadores na fila e ele os obterá e executará - não há proteção contra isso: obtenha - execute, obtenha - cumprir e assim por diante ad infinitum.
Somente na fase de pesquisa, se obtemos constantemente um evento de E / S, o tempo todo alguém escreve algo no soquete para que possamos executar pelo menos timers e setImmediate, ele tem proteção de limite e depende do sistema. Ou seja, será diferente em diferentes sistemas operacionais.
Infelizmente, outras fases, incluindo temporizadores e setImmediate,
não possuem essa proteção. Portanto, se você fizer como no exemplo, tudo irá congelar e não atingirá a fase de votação por um período muito longo.
Mas você acha que algo mudará se substituirmos setTimeout (() => parseData (fatia), 0) por setImmediate (() => parseData (fatia))? - Naturalmente, não, também não há proteção na fase de verificação lá.
Para resolver esse problema, você pode chamar o
processamento recursivo .
const parseData = (slice) =>
A conclusão é que pegamos a função parseData e escrevemos sua chamada recursiva, mas não apenas a nós mesmos, mas através de setImmediate. Quando você chama isso na fase setImmediate, ele passa para o próximo tick, e não para o atual. Portanto, isso liberará o loop de eventos, além de um círculo. Ou seja, temos recursiveAsyncParseData, onde passamos um determinado índice, obtemos o pedaço por esse índice, o analisamos - e depois colocamos a fila setImmediate no próximo índice. Chegará ao nosso próximo tick e podemos processar recursivamente tudo isso.
É verdade que o problema é que isso ainda é algum tipo de tarefa vinculada à CPU. Talvez ela ainda de alguma forma pese e gaste tempo no Event Loop. Provavelmente você deseja que seu Node.js seja puramente vinculado a E / S.
Portanto, é melhor usar outras coisas, por exemplo, conjunto de
bifurcação / thread de processo.Agora sabemos sobre o Node.js que:
- tudo é distribuído em fases - bem, nós sabemos disso claramente;
- existe proteção contra a fase de votação muito longa, mas não o resto;
- padrões de processamento recursivo podem ser aplicados para não bloquear o loop de eventos;
- Mas é melhor usar o fork do processo, o pool de threads, o processo filho
Você também deve ter cuidado com o pool de encadeamentos, porque o Node.js inicia as tarefas lá em cima, em particular a resolução de DNS, porque para Linux, por algum motivo, a função de resolução de DNS não é assíncrona. Portanto, ele deve ser executado no ThreadPool. No Windows, felizmente, não é assim. Mas você pode ler arquivos de forma assíncrona. No Linux, infelizmente, é impossível.
Na minha opinião, o limite padrão é de 4 processos no ThreadPool. Portanto, se você fizer algo ativamente lá, ele competirá com todos os outros - com fs e outros. Você pode considerar aumentar o ThreadPool, mas também com muito cuidado. Então, leia algo sobre este tópico.
Microtask: circulação pulmonar
Temos tarefas no Node.js e tarefas nos navegadores. Você já deve ter ouvido falar em microtask. Vamos ver o que é e como eles funcionam e começar com os navegadores.
Microtask em navegadores
Para entender como o microtask funciona, passamos ao algoritmo do loop de eventos de acordo com o padrão whatwg, ou seja, vamos à especificação e ver como tudo se parece.

Traduzindo para a linguagem humana, parece algo como isto:
- Faça a tarefa gratuita da nossa linha
- Nós realizamos
- Realizamos o ponto de verificação microtask - OK, ainda não sabemos o que é, mas nos lembramos.
- Atualizamos a renderização (se necessário) e retornamos à estaca zero.

Eles são realizados no local indicado no diagrama e em vários outros locais, sobre os quais aprenderemos em breve. Ou seja, a tarefa terminou, microtask é executado.
Fontes de microtucks
Importante - não a própria Promessa, ou seja, Promessa. O retorno de chamada que foi colocado então é uma microtask. Se você ligou para 10 então - você tem 10 microcarros, 10 mil então - 10 mil microcarros.
- Observador de mutações.
- Object.observe , que está obsoleto e ninguém precisa.
Quantos usam o observador da Mutação?
Eu acho que poucos usam o observador da Mutação. Muito provavelmente, Promise.then é usado mais, é por isso que o consideraremos no exemplo.
Recursos do ponto de verificação microtask:- Fazemos tudo - isso significa que executamos todas as microtaks que temos na fila até o final. Não abandonamos nada - apenas pegamos e fazemos tudo o que é, eles devem ser micro, certo?
- Você ainda pode gerar uma nova microtask no processo, e elas serão executadas no mesmo ponto de verificação da microtask.
- O que também é importante - eles são executados não apenas após a execução da tarefa, mas também após a limpeza da pilha.
Este é um ponto interessante. Acontece que é possível gerar novas microtarefas e todos nós as cumpriremos até o fim. O que isso pode nos levar?

Nós temos dois corações. Eu animei o primeiro coração com animação JS e o segundo com animação CSS. Há outro ótimo recurso chamado starveMicrotasks. Chamamos Promise.resolve e, em seguida, colocamos a mesma função.
Veja na
apresentação o que acontece se você chamar esta função.
Sim, o coração do JS irá parar, porque adicionamos uma microtask e, em seguida, adicionamos uma microtask e, em seguida, adicionamos uma microtask ... E assim infinitamente.
Ou seja, a chamada recursiva dos microtucks vai travar tudo. Mas parece que eu tenho tudo assíncrono! Deve ser liberado, chamei setTimeout lá. Não! Infelizmente, você precisa ter cuidado com o microtask; portanto, se você usar uma chamada recursiva de alguma forma, tenha cuidado - poderá bloquear tudo.
Além disso, como lembramos, o microtask é executado no final da limpeza da pilha. Lembramos o que é uma pilha. Acontece que, assim que saímos do nosso código, o retorno de chamada setTimeout foi executado - é isso - as microtasks foram para lá. Isso pode levar a efeitos colaterais interessantes.
Considere
um exemplo .

Há um botão e um recipiente cinza no qual está localizado. Assinamos o clique do botão e do contêiner.
Eventos, como sabemos, surgem, ou seja, eles aparecem lá e ali.Nos manipuladores, fazemos duas coisas:- Promise.resolve;
- . em que console.log ('RO') é inserido
Então, no próprio manipulador, entramos em “FUS” e no manipulador no contêiner - “DAH!” (quando o nosso evento aparecer).O que você acha que aparecerá no nosso console? Essas mensagens têm uma pequena dica e, curiosamente, ele exibirá "FUS RO DAH!" Ótimo! , .

, , . – . , - ?

! .

, .
, , , . ,
.
- — buttonHandleClick, .
- Promise.resolve. . , console.log('RO') . .
- console.log('FUS').
- buttonHandleClick . .
- , (divHandleClick) , «DAH!».
- HandleClick .
, . ?
:
- button.click(). .
- button HandleClick.
- Promise.resolve then. , Promise.resolve .
- console.log «FUS».
- buttonHandleClick , .
(click) , , . divHandleClick , , console.log('DAH!') . , .
, , button.click .
. , , . , , .
: () ( ). - , , stopPropagation. , , , , - , .
, - ( junior-) — «», promise, , then , - . ,
, : , , . . , - .
( 4) , . , , , , - . .
, :- Event Loop. Isso é desagradável.
- , .
, . — , , .
Node.js
Node.js Promise.then process.nextTick. , — . , , , , .
process.nextTick
, process.nextTick, setImmediate? Node.js ?
. createServer, EventEmitter, , listen ( ), .
const createServer = () => { const evEmitter = new EventEmitter() return { listen: port => { evEmitter.emit('listening', port) return evEmitter } } } const server = createServer().listen(8080) server.on('listening', () => console.log('listening'))
, , 8080, listening console.log - .
, , - .
createServer, . listen, , . .
, , . ? process.nextTick: evEmitter.emit('listening', port) process.nextTick(() => evEmitter.emit('listening', port)).
,
process.nextTick , . EventEmitter, . , , API, . process.nextTick, emit , userland . createServer, , listen, listening. — process.nextTick — ! , , .
process.nextTick . , .
, process.nextTick , Promise.then . process.nextTick , — , Event Loop, Node.js. , , .
process.nextTick , ghbvtybnm setImmediate , C++ .. process.nextTick .
Async/await
API — async/await, - . . , async/await Promise, Event Loop . , .
, !Frontend Conf — 4 5 , . , :
Venha, vai ser interessante!