O JavaScript é uma linguagem de programação que pode ser bastante confusa para quem está começando, principalmente quando se trata de callbacks e promises. Vejamos um exemplo de código abaixo:
function printHelloWorld() {
console.log("Hello World");
}
function sayHi() {
console.log("Hi!");
}
function blockFor7000ms() {
for (let i = 0; i < 1000000000; i++) {}
console.log("end for");
}
setTimeout(printHelloWorld, 0);
blockFor7000ms();
new Promise((resolve) => {
sayHi();
resolve();
});
Se executarmos esse código, teremos essa resposta no console:
> end for
> Hi!
> Hello World
Por que o end for
foi executado primeiro, mesmo que ele tenha demorado 7000ms? O setTimeout
não deveria ter sido executado primeiro? E por que a Promise
também foi executada antes do setTimeout
?
Para responder a essas perguntas, primeiro temos que entender alguns princípios básicos do JavaScript.
Event Loop e Call Stack
O Call Stack e o Event Loop são dois dos principais mecanismos que trabalham juntos para gerenciar a execução de código em JavaScript. O Call Stack é uma estrutura de dados que rastreia a execução das funções no código. Quando uma função é chamada, ela é adicionada ao topo do Call Stack. Quando a função é concluída, ela é removida do Call Stack e o controle é devolvido para a função que a chamou.
O Event Loop, por outro lado, é responsável por gerenciar a fila de eventos. Eventos, como interações do usuário ou respostas de rede, são adicionados à fila de eventos. O Event Loop monitora constantemente a fila de eventos e, quando o Call Stack está vazio, o próximo evento da fila é retirado e processado.
Quando o JavaScript executa um trecho de código, ele é adicionado ao Call Stack. Se o código contém operações síncronas que exigem tempo de processamento, o Call Stack pode ser bloqueado até que as operações sejam concluídas. No entanto, se o código contém operações assíncronas, como uma chamada de API ou manipulação de eventos do usuário, essas operações são adicionadas à fila de eventos em vez de serem executadas imediatamente.
Quando uma operação assíncrona é concluída, ela é adicionada à fila de tarefas, juntamente com outras operações assíncronas que foram concluídas. O Event Loop monitora a fila de tarefas, dando prioridade às micro-tarefas em relação às macro-tarefas. As micro-tarefas são executadas antes das macro-tarefas, mesmo que tenham sido adicionadas posteriormente.
Depurando nosso codigo
Sabendo disso, vamos depurar nosso código para compreender melhor esses conceitos.
1. Até a linha 13, o JavaScript apenas salvou em memória nossas funções. Na linha 14, ele adicionará no topo do nosso call stack a função do setTimeout
. O setTimeout
faz parte das features do browser, logo, ele será executado pelo browser após o tempo que foi passado como parâmetro. Como foi passado 0 segundos, ele é executado imediatamente. O browser envia a nossa função printHelloWorld
para o Callback Queue (macro tarefas), que ficará aguardando até que não haja mais nada no call stack.
2. Em seguida, a função blockFor7000ms()
é adicionada ao call stack. Esta função consiste em um loop for que itera um grande número de vezes. Isso levará um tempo significativo para ser concluído e, enquanto isso, o restante do código não será executado, bloqueando o nosso call stack. E a nossa função printHelloWorld
continuará no Callback Queue aguardando para ser executada. Se olharmos para o console nesse ponto, ele acabará de imprimir o nosso end for
quando a função blockFor7000ms()
finalizar.
> end for
3. Finalizando o blockFor7000ms
, ele adicionará a nossa Promise
para o call stack. Ao executar a nossa Promise, ele enviará a função sayHi()
para a fila de micro-tarefas.
4. Depois disso, o nosso programa entra em um estado de espera, aguardando que o call stack fique vazio. Isso acontece porque a execução da Promise é assíncrona e não bloqueia o call stack.
5. Agora, o event loop verificará que não tem mais nada no call stack. Ele verificará primeiro na fila de micro-tarefas. Nessa fila, ele encontrará a função sayHi()
e a passará para o call stack. No Call stack, a função sayHi()
será executada e irá imprimir o nosso console.log. Se olharmos no console, ficará assim:
> end for
> Hi!
6. Nesse ponto, o nosso call stack ficará vazio novamente, e o nosso event loop verificará primeiro a nossa fila de micro-tarefas. Como não teremos nada, ele verificará as nossas macro-tarefas. Nas macro-tarefas, ele encontrará o nosso printHelloWorld
e o passará para o call stack, executando-o e imprimindo o console.log. Por fim, nosso console ficará assim:
> end for
> Hi!
> Hello World
Conclusão
Em um primeiro momento javascript pode ser bastante obscuro e confuso, mas isso se deve a forma como ele funciona, se entendermos a teoria veremos q ele é bastante simples e poderoso.
Ref:
https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Guide/Using_promises#composi%C3%A7%C3%A3o