</> HTML5Advent
ENFRESDEITPT

// js · Web Platform Advent #3

async/await em JavaScript: um guia prático

Como async e await funcionam, como se relacionam com as promises, o tratamento de erros com try/catch, o await em laços e a armadilha serial-vs-paralelo — com exemplos executáveis.

As mãos de um desenvolvedor digitando num teclado, com código-fonte colorido no monitor

As palavras-chave async e await são sintaxe sobre as promises. Elas permitem escrever código assíncrono que se lê de cima para baixo como código síncrono, mas continuando não bloqueante por baixo dos panos. Se você conhece promises, já conhece async/await — ele apenas remove a cerimônia do .then().

O básico

Marque uma função como async e você poderá usar await dentro dela. await pausa essa função até a promise ser decidida, e então lhe entrega o valor resolvido:

async function getUser(id) {
  const res = await fetch('/api/user/' + id);
  const user = await res.json();
  return user.name;
}

getUser(42).then((name) => console.log(name));

Duas coisas para lembrar: uma função async sempre retorna uma promise (o que você retornar é envolvido em uma), e await só funciona dentro de uma função async — exceto no nível superior de um módulo ES, onde o await de nível superior é permitido.

async/await vs promises

É a mesma maquinaria. Esta cadeia de promises…

fetch('/api/user')
  .then((res) => res.json())
  .then((user) => console.log(user.name));

…é exatamente equivalente a esta versão async, que a maioria das pessoas acha mais fácil de ler e depurar:

const res = await fetch('/api/user');
const user = await res.json();
console.log(user.name);

Você pode misturá-las livremente — faça await de qualquer promise, inclusive do resultado de Promise.all().

Tratamento de erros com try/catch

Com async/await você captura promises rejeitadas usando o try/catch comum, a mesma construção que usa para erros síncronos:

async function loadUser(id) {
  try {
    const res = await fetch('/api/user/' + id);
    if (!res.ok) throw new Error('HTTP ' + res.status);
    return await res.json();
  } catch (err) {
    console.error('Could not load user:', err.message);
    return null;
  } finally {
    console.log('loadUser finished');
  }
}

Um await rejeitado lança uma exceção naquela linha, então o bloco catch roda. O bloco finally roda quer tenha ocorrido um erro ou não.

Uma autoestrada de várias faixas com carros e caminhões viajando lado a lado em paralelo
Tarefas independentes podem rodar lado a lado como veículos em faixas paralelas — aguardá-las uma de cada vez impõe, em vez disso, uma fila única.

A grande armadilha: await serial vs paralelo

O erro mais comum com async/await é aguardar tarefas independentes uma após a outra. Isso roda em série — a segunda requisição não começa até a primeira terminar:

// Slow: total time = a + b + c
const a = await fetchA();
const b = await fetchB();
const c = await fetchC();

Se as tarefas não dependem umas das outras, inicie-as todas primeiro e depois aguarde-as juntas com Promise.all. Agora elas rodam em paralelo:

// Fast: total time ≈ the slowest one
const [a, b, c] = await Promise.all([fetchA(), fetchB(), fetchC()]);

await em laços

Um laço for simples com await processa os itens um de cada vez, o que é correto quando cada passo depende do anterior ou você precisa evitar sobrecarregar um servidor:

for (const id of ids) {
  const item = await fetchItem(id);   // sequential — one after another
  console.log(item);
}

Para processá-los de forma concorrente, mapeie para um array de promises e faça await Promise.all. Note que forEach não espera por callbacks async — use map + Promise.all em vez disso:

const items = await Promise.all(ids.map((id) => fetchItem(id)));

Referência rápida

ObjetivoPadrão
Aguardar uma promiseconst x = await p;
Tratar errostry { await ... } catch (e) { ... }
Rodar tarefas independentes em paraleloawait Promise.all([...])
Laço sequencialfor...of com await
Laço concorrentemapPromise.all

async/await não substitui as promises — torna-as legíveis. Mantenha Promise.all na sua caixa de ferramentas para o paralelismo, recorra a try/catch para os erros e fique atento ao await serial acidental, e você terá o JavaScript assíncrono bem sob controle.