Como começar rapidamente com VUE3.0: Aprenda
Embora o JavaScript seja de thread único, o loop de eventos usa o kernel do sistema tanto quanto possível, permitindo que o Node.js execute operações de E/S sem bloqueio. Embora a maioria dos kernels modernos sejam multithread, eles podem lidar com tarefas multithread no. fundo. Quando uma tarefa é concluída, o kernel informa ao Node.js e, em seguida, o retorno de chamada apropriado será adicionado ao loop para execução. Este artigo apresentará este tópico com mais detalhes.
Quando o Node.js inicia a execução, o loop de eventos
será inicializado primeiro, processe o script de entrada fornecido (ou coloque-o no REPL, o que não é abordado neste documento). Isso executará uma chamada de API assíncrona, agendará um cronômetro ou chamará process.nextTick() e, em seguida, executará uma chamada de API assíncrona. comece a processar o loop de eventos A
figura a seguir mostra a sequência de execução do loop de eventos
┌──────────────────────────┐.
┌─>│ temporizadores │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ retornos de chamada pendentes │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ ocioso, prepare │ │ └─────────────┬────────────┘ ┌────────── ──────┐ │ ┌─────────────┴────────────┐ │ entrada: │ │ │ enquete │<─────┤ conexões, │ │ └─────────────┬─────────────┘ │ dados, etc. │ ┌─────────────┴────────────┐ └────────── ──────┘ │ │ verificar │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ fechar retornos de chamada │ Noentanto,
cada caixa representa um estágio do loop de eventos.
Cada estágio tem uma execução de retorno de chamada da fila FIFO. , cada estágio é executado de maneira própria, quando o loop de eventos entra em um estágio, ele executa qualquer operação no estágio atual e começa a executar retornos de chamada na fila do estágio atual até que a fila seja completamente consumida ou executada. os dados máximos. Quando a fila se esgota ou atinge seu tamanho máximo, o loop de eventos passa para a próxima fase.
TemporizadoresEm cada processo do loop de eventos, O Node .js verifica se está aguardando E/S assíncrona e um timer e, se não, desliga
Um timer especifica o ponto crítico no qual um retorno de chamada será executado, em vez do horário desejado
.para ser executado. O cronômetro será executado o mais rápido possível após o tempo decorrido especificado; no entanto, o agendamento do sistema operacional ou outros retornos de chamada podem atrasar a execução.
Tecnicamente falando, a fase de pesquisa determina quando o retorno de chamada é executado.
Por exemplo, você define um cronômetro para execução após 100 ms, mas seu script lê um arquivo de forma assíncrona e leva 95 ms
const fs = require('fs') ; function someAsyncOperation(retorno de chamada) { // Suponha que isso leve 95ms para ser concluído fs.readFile('/caminho/para/arquivo', retorno de chamada); } const timeoutScheduled = Date.now(); setTimeout(() => { const atraso = Date.now() - timeoutScheduled; console.log(`${delay}ms se passaram desde que fui agendado`); }, 100); //faz someAsyncOperation que leva 95 ms para ser concluído someAsyncOperation(() => { const startCallback = Date.now(); //faça algo que levará 10ms... while (Date.now() - startCallback <10) { //não faço nada } });
Quando o loop de eventos entra na fase de pesquisa, há uma fila vazia (fs.readFile() ainda não foi concluída), então ele aguardará os milissegundos restantes até que o limite do temporizador mais rápido seja atingido. .readFile() concluiu a leitura do arquivo e levará 10 ms para adicionar à fase de pesquisa e concluir a execução. Quando o retorno de chamada for concluído, não haverá retornos de chamada na fila para serem executados e o loop de eventos retornará à fase de temporizadores. para executar o retorno de chamada do temporizador. Neste exemplo, você verá que o cronômetro está atrasado 105 ms antes de ser executado.
Para evitar que a fase de pesquisa bloqueie o loop de eventos, a libuv (a biblioteca da linguagem C que implementa o loop de eventos e todo o comportamento assíncrono na plataforma) também possui. uma fase de poll. max stop polling mais eventos
Esta fase executa retornos de chamada para determinadas operações do sistema (como tipos de erro TCP). Por exemplo, alguns sistemas *nix desejam esperar que um erro seja relatado se um soquete TCP receber ECONNREFUSED ao tentar se conectar. Isso será colocado na fila para execução durante a fase de retorno de chamada pendente.
A fase de poll tem duas funções principais
o loop de eventos entra na fase de poll e não há temporizador, as duas coisas a seguir acontecem.
chamada
detectará se o cronômetro expirou. Nesse caso, o loop de eventos alcançará o estágio de cronômetros e executará a
. Este estágio permite que as pessoas executem retornos de chamada imediatamente após a conclusão da fase de pesquisa. Se a fase de pesquisa ficar ociosa e o script tiver sido enfileirado usando setImmediate(), o loop de eventos poderá continuar para a fase de verificação em vez de esperar.
setImmediate() é na verdade um temporizador especial que é executado em uma fase separada do loop de eventos. Ele usa uma API libuv para agendar retornos de chamada a serem executados após a conclusão da fase de pesquisa.
Normalmente, à medida que o código é executado, o loop de eventos eventualmente atinge a fase de pesquisa, onde aguarda conexões de entrada, solicitações, etc. No entanto, se um retorno de chamada for agendado usando setImmediate() e a fase de sondagem ficar ociosa, ele terminará e continuará com a fase de verificação em vez de aguardar pelo evento de sondagem.
Se um soquete ou operação for fechado repentinamente (por exemplo, socket.destroy()), o evento close será enviado para este estágio, caso contrário, será enviado via process.nextTick() setImmediate
setImmediate() e setTimeout() são semelhantes, mas se comportam de maneira diferente dependendo de quando são chamados
A ordem na qual cada retorno de chamada é executado depende de a ordem em que são chamados Neste ambiente, se o mesmo módulo for chamado ao mesmo tempo, o tempo será limitado pelo desempenho do processo (isso também será afetado por outros aplicativos em execução nesta máquina,
por exemplo
)., se não executarmos o seguinte script em I/O, embora seja afetado pelo desempenho do processo, não é possível determinar a ordem de execução desses dois temporizadores:
//timeout_vs_immediate.js setTimeout(() => { console.log('tempo limite'); }, 0); setImmediate(() => { console.log('imediato'); });
$ nó timeout_vs_immediate.js tempo esgotado imediato $ nó timeout_vs_immediate.js imediato timeout
No entanto, se você passar para o loop de E/S, o retorno de chamada imediato sempre será executado primeiro
// timeout_vs_immediate.js const fs = requer('fs'); fs.readFile(__nomedoarquivo, () => { setTimeout(() => { console.log('tempo limite'); }, 0); setImmediate(() => { console.log('imediato'); }); });
$ nó timeout_vs_immediate.js imediato tempo esgotado $ nó timeout_vs_immediate.js imediatoA vantagem do
timeout
setImmediate sobre setTimeout é que setImmediate sempre será executado primeiro, antes de qualquer timer em E/S, independentemente de quantos timers existirem.
Embora process.nextTick() faça parte da API assíncrona, você deve ter notado que ele não aparece no diagrama. Isso ocorre porque process.nextTick() não faz parte da tecnologia de loop de eventos. a operação atual é executada Após a conclusão, nextTickQueue será executado independentemente do estágio atual do loop de eventos. Aqui, as operações são definidas como transformações do manipulador C/C++ subjacente e manipulam o JavaScript que precisa ser executado. De acordo com o diagrama, você pode chamar process.nextTick() em qualquer estágio. Todos os retornos de chamada passados para process.nextTick() serão executados antes que o loop de eventos continue a execução. . process.nextTick() "morre de fome" sua E/S, o que impede que o loop de eventos entre na fase de pesquisa.
Por que essa situação está incluída no Node.js? Como a filosofia de design do Node.js é que uma API deve sempre ser assíncrona, mesmo que não seja necessário, observe o seguinte trecho
function apiCall(arg, callback) { if (tipo de arg! == 'string') retornar processo.nextTick( ligar de volta, new TypeError('argumento deve ser string') ); }
O snippet faz a verificação dos parâmetros e, se estiver incorreto, passa o erro para o callback. A API foi atualizada recentemente para permitir a passagem de parâmetros para process.nextTick() permitindo aceitar quaisquer parâmetros passados após o retorno de chamada como argumentos para o retorno de chamada, para que você não precise aninhar funções.
O que estamos fazendo é passar o erro de volta ao usuário, mas somente se permitirmos que o restante do código do usuário seja executado. Ao usar process.nextTick(), garantimos que apiCall() sempre execute seu retorno de chamada após o restante do código do usuário e antes de permitir que o loop de eventos continue. Para conseguir isso, a pilha de chamadas JS pode se desenrolar e, em seguida, o retorno de chamada fornecido é executado imediatamente, o que permite fazer chamadas recursivas para process.nextTick() sem atingir RangeError: Maximum call stack size excedeu a partir da v8.