Médio JavaScript

Prever a ordem com async/await e setTimeout

Enunciado

Dado o código abaixo, qual será a ordem de impressão? Escreva a saída exata e explique por que essa ordem ocorre. Depois, modifique o código para que a saída seja: 1, 2, 3, 4 (sem alterar a ordem dos console.log originais).

console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);

Requisitos

  • A saída original deve ser: 1, 4, 3, 2
  • Explicar a ordem com base no event loop
  • Modificar o código para que a saída seja: 1, 2, 3, 4
  • A modificação não pode alterar a ordem dos console.log originais (pode adicionar código em volta)

Código inicial

// Código original
console.log(1);
setTimeout(() => console.log(2), 0);
Promise.resolve().then(() => console.log(3));
console.log(4);

// Modifique abaixo para obter 1, 2, 3, 4

Saída esperada

1
4
3
2
Ver dica

Para forçar a ordem 1, 2, 3, 4, você pode aninhar as chamadas dentro de callbacks ou usar await para sequenciar.

Mostrar solução

Saída original: 1, 4, 3, 2

Explicação:

  • console.log(1) e console.log(4) são síncronos e executam primeiro.
  • Promise.then agenda microtarefa, que roda antes de macrotarefas.
  • setTimeout agenda macrotarefa, que roda por último.

Código modificado para 1, 2, 3, 4:

// Modificação: aninhar os callbacks para forçar a ordem
console.log(1);
setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3);
    console.log(4);
  });
}, 0);

Passo a passo

  1. O código original imprime 1 e 4 sincronamente.
  2. A microtarefa (Promise) imprime 3 antes da macrotarefa (setTimeout) que imprime 2.
  3. Para forçar 1, 2, 3, 4, colocamos o console.log(2) dentro do setTimeout.
  4. Dentro do setTimeout, após imprimir 2, agendamos uma microtarefa com Promise.resolve().then.
  5. Essa microtarefa imprime 3 e, em seguida, imprime 4.
  6. Assim, a ordem final é 1 (síncrono), 2 (macrotarefa), 3 e 4 (microtarefa dentro da macrotarefa).

Por que funciona

Ao aninhar as chamadas, controlamos a ordem de execução. O setTimeout é executado após as microtarefas iniciais, mas como não há microtarefas pendentes no momento, ele executa 2. Dentro dele, agendamos uma microtarefa que executa 3 e 4 imediatamente após, pois microtarefas têm prioridade.

Erros comuns

  • Tentar usar await fora de função async: Causa erro de sintaxe.
  • Esquecer que setTimeout é macrotarefa: Achar que ele executa antes de Promises.
  • Modificar a ordem dos console.log originais: O enunciado proíbe, então é preciso reorganizar o fluxo com callbacks.

Outra forma de resolver

Usar async/await para sequenciar:

async function executar() {
  console.log(1);
  await new Promise(resolve => setTimeout(resolve, 0));
  console.log(2);
  await Promise.resolve();
  console.log(3);
  console.log(4);
}
executar();

Essa abordagem é mais legível para sequências assíncronas complexas.

Saída esperada

1
4
3
2