Entendendo JavaScript: Micro e Macro Tasks

Entendendo JavaScript: Micro e Macro Tasks

E aí, pessoal! Se você é um desenvolvedor web ou apenas alguém curioso sobre programação, com certeza já ouviu falar de JavaScript. É uma linguagem poderosa que impulsiona a web e permite que sites interajam com você de maneira dinâmica. Mas você já ouviu falar sobre micro e macro tasks em JavaScript? E sabe como a função queueMicrotask() se encaixa nesse cenário? Não se preocupe se isso parece grego para você agora, porque neste artigo, vamos desvendar esses conceitos de uma forma descontraída e divertida.

Vamos lá!

A Magia do JavaScript

Antes de mergulharmos nas tarefas (tasks), é importante entender o JavaScript em um nível mais básico. JavaScript é uma linguagem de programação amplamente utilizada para criar interações dinâmicas em sites e aplicativos web. Ele permite que você faça coisas incríveis, como animações suaves, atualizações de página sem recarregamento e até mesmo jogos online.

Uma característica fundamental do JavaScript é que ele é single-threaded, o que significa que ele processa uma coisa de cada vez. Imagine-o como um atendente de telemarketing (não, não odeie o JavaScript por isso) que só pode lidar com uma chamada de cada vez. Isso pode parecer limitante, mas o JavaScript usa um mecanismo de event loop para lidar com tarefas concorrentes de uma maneira inteligente.

O Event Loop

O event loop é o cérebro por trás do funcionamento assíncrono do JavaScript. Ele permite que o JavaScript execute tarefas em segundo plano enquanto continua a responder a eventos do usuário e a fazer outras coisas. Imagine um chef de cozinha que está cozinhando várias receitas ao mesmo tempo, mas sempre dá uma olhada na fila de pedidos para ver se há algo novo a fazer.

No entanto, para entender completamente o event loop, precisamos dar uma olhada mais de perto em como as tarefas são organizadas em JavaScript.

Micro Tasks e Macro Tasks

Em JavaScript, temos dois tipos principais de tarefas que desempenham um papel fundamental na gestão de código assíncrono: as micro tasks e as macro tasks. Vamos explorar o que são essas tarefas e como funcionam no contexto do JavaScript.

Em JavaScript, existem dois tipos principais de tarefas que desempenham um papel fundamental na gestão de código assíncrono: as micro tasks e macro tasks. Vamos explorar o que são essas tarefas e como funcionam no contexto do JavaScript.

Micro Tasks

Uma micro task, como definida pelo MDN, é uma função curta que é executada após a conclusão da função ou programa que a gerou, mas antes de retornar o controle ao event loop. O principal destaque das micro tasks é sua alta prioridade, pois elas são executadas imediatamente quando a pilha de execução do JavaScript (call stack) está vazia. Elas são colocadas na fila de micro tasks para execução posterior.

Vamos dar uma olhada em um exemplo de código que utiliza uma micro task:

console.log('Início do código');

Promise.resolve().then(() => {
  console.log('Micro Task executada!');
});

console.log('Fim do código');

Neste exemplo, a micro task é agendada para ser executada após a execução das outras instruções. Portanto, a saída será a seguinte:

Início do código
Fim do código
Micro Task executada!

Macro Tasks

As macro tasks, também conhecidas como tasks, representam outra categoria essencial de tarefas no JavaScript. Uma macro task é qualquer código programado para ser executado pelo mecanismo padrão do JavaScript, como o início da execução de um programa, eventos que acionam callbacks, timeouts e intervalos. As macro tasks são adicionadas à fila de tarefas (macro tasks queue).

Ao executar tasks da fila de tarefas, o runtime executa cada tarefa que está na fila no momento em que uma nova iteração do event loop começa. As tarefas adicionadas à fila após o início da iteração não serão executadas até a próxima iteração.

Vamos demonstrar o uso de uma macro task com um exemplo simples:

console.log('Início do código');

setTimeout(() => {
  console.log('Macro Task executada!');
}, 0);

console.log('Fim do código');

Neste exemplo, estamos usando setTimeout para adicionar uma macro task que será executada após um tempo de espera de 0 milissegundos. Mesmo que o tempo de espera seja zero, a macro task ainda é tratada como uma tarefa de menor prioridade e é colocada no final da fila. A saída será a seguinte:

Início do código
Fim do código
Macro Task executada!

Função queueMicrotask()

Agora que já entendemos os conceitos de micro tasks e macro tasks, vamos introduzir a função queueMicrotask(). Esta função oferece uma maneira explícita de agendar micro tasks em seu código JavaScript. Ela aceita uma função como argumento e a adiciona à fila de micro tasks para ser executada quando a pilha de chamadas de função estiver vazia.

No entanto, é crucial mencionar que a função queueMicrotask() deve ser usada com cuidado. O agendamento excessivo de micro tasks pode resultar em um comportamento inesperado e impactar negativamente o desempenho de seu aplicativo. Portanto, é fundamental aplicar essa função com discernimento, reservando-a para situações em que seja necessário garantir que uma micro task seja executada imediatamente após as operações atuais.

Aqui está um exemplo de como usar queueMicrotask():

console.log('Início do código');

queueMicrotask(() => {
  console.log('Micro Task agendada com queueMicrotask()!');
});

console.log('Fim do código');

Neste exemplo, utilizamos queueMicrotask() para agendar uma micro task que será executada após as outras instruções. A saída será a seguinte:

Início do código
Fim do código
Micro Task agendada com queueMicrotask()!

A função queueMicrotask() é útil quando você precisa garantir que uma micro task seja executada imediatamente após a conclusão das operações atuais, independentemente do contexto em que você está trabalhando.

Comportamento de Execução de Micro Tasks e Macro Tasks

A diferença no comportamento de execução entre micro tasks e macro tasks é um aspecto crítico a ser compreendido. É importante entender que, após a conclusão de cada macro task, o event loop verifica se a tarefa está retornando o controle para o código JavaScript. Se não, todas as micro tasks pendentes são executadas antes de qualquer outra macro task ser considerada.

Essa peculiaridade é o que garante que as micro tasks tenham prioridade e sejam tratadas imediatamente, tornando-as ideais para tarefas críticas que exigem resposta rápida. Porém, isso também pode ser perigoso, pois se ao processar as micro tasks você estiver continuamente adicionando outras micro tasks à fila, isso irá causar um bloqueio do seu código principal.

Mas agora que entendemos o básico das micro tasks, macro tasks e da função queueMicrotask(), vamos dar uma olhada em um exemplo prático que demonstra como elas funcionam em conjunto com o event loop.

console.log('Início do código');
let i;

for (i = 0; i < 5; i++) {

  setTimeout(() => {
    console.log('i:', i);
  }, 0);

}

Promise.resolve().then(() => {
  console.log('Micro Task executada!');
});

queueMicrotask(() => {
  console.log('Micro Task agendada com queueMicrotask()!');
});

console.log('Fim do código');

Neste exemplo, você verá algo interessante acontecendo. O loop for está configurado para criar cinco setTimeouts e duas micro tasks (a Promise.resolve() e queueMicrotask()).

Aqui está o que acontece passo a passo:

  1. O código começa a ser executado, e "Início do código" é impresso.
  2. Uma variável i é declarada no escopo global.
  3. Em seguida, o código entra em um loop que vai de 0 até 4 (5 vezes no total) usando a variável i como contador.
  4. Dentro do loop, é chamada a função setTimeout cinco vezes. Cada chamada do setTimeout agenda uma macro task para ser executada após um tempo de espera de 0 milissegundos. Essa função imprimirá "i:" seguido do valor atual de i.
  5. O loop continua executando e incrementando i em cada iteração até que i atinja o valor de 5.
  6. Após o término do loop, a variável i agora possui o valor 5.
  7. Logo após o loop for, há uma chamada Promise.resolve().then(). Isso cria uma micro task que imprimirá "Micro Task executada!".
  8. A função queueMicrotask() também é chamada, agendando outra micro task para imprimir "Micro Task agendada com queueMicrotask()!".
  9. Neste ponto, a execução do código principal chegou ao fim, e "Fim do código" é impresso.

Aqui está o que acontece em relação às micro tasks e macro tasks:

  • Durante o loop for, cinco macro tasks são agendadas por meio do setTimeout. Elas não são executadas imediatamente.
  • Duas micro tasks são agendadas: uma pela promessa e outra por queueMicrotask(). Lembre-se que micro tasks têm prioridade sobre macro tasks.

Agora, a ordem de execução:

  1. A call stack está vazia, então o event loop verifica a fila de micro tasks. A primeira micro task, que é a promise, é executada, e "Micro Task executada!" é impresso.
  2. A segunda micro task, agendada por queueMicrotask(), é executada, e "Micro Task agendada com queueMicrotask()!" é impresso.
  3. Com todas as micro tasks processadas, o event loop volta a verificar a fila de macro tasks.
  4. As cinco macro tasks agendadas pelos setTimeouts agora são executadas. No entanto, o valor de i é 5 para todas elas, porque o loop já terminou e i foi incrementado até 5.
  5. "i: 5" é impresso cinco vezes, uma vez para cada macro task.

A saída final será algo semelhante a isto:

Início do código
Fim do código
Micro Task executada!
Micro Task agendada com queueMicrotask()!
i: 5
i: 5
i: 5
i: 5
i: 5

Este exemplo ilustra como as micro tasks têm prioridade sobre as macro tasks e como a ordem de execução pode afetar o resultado final, especialmente em situações em que variáveis como i estão envolvidas. Certifique-se de compreender esses conceitos ao trabalhar com código assíncrono em JavaScript para evitar surpresas indesejadas.

Ao infinito e além!

E aí está, pessoal! Agora você tem uma compreensão mais profunda de como o JavaScript lida com tarefas assíncronas usando micro tasks e macro tasks, bem como como a função queueMicrotask() pode ser usada para agendar micro tasks explicitamente. O event loop é o que permite que o JavaScript seja tão dinâmico e responsivo, mesmo sendo uma linguagem single-threaded.

Lembre-se de que as micro tasks têm prioridade sobre as macro tasks, e isso pode ser importante ao lidar com código assíncrono complexo. Entender esses conceitos é fundamental para se tornar um mestre em JavaScript e criar aplicativos web incríveis.No entanto, tenha cuidado ao usar queueMicrotask() e não a utilize em excesso, pois isso pode afetar o desempenho do seu aplicativo.

E, claro, continue explorando e se divertindo com a programação em JavaScript, mas com responsabilidade! JavaScript é como uma caixa de ferramentas mágica, e agora você tem algumas ferramentas a mais para criar suas próximas obras-primas na web.

Divirta-se codificando! 🚀🌐

💡
As opiniões e comentários expressos neste artigo são de propriedade exclusiva de seu autor e não representam necessariamente o ponto de vista da Revelo.

A Revelo Content Network acolhe todas as raças, etnias, nacionalidades, credos, gêneros, orientações, pontos de vista e ideologias, desde que promovam diversidade, equidade, inclusão e crescimento na carreira dos profissionais de tecnologia.