Nesta aula, vamos explorar o async/await, uma sintaxe moderna do JavaScript que simplifica o trabalho com promises. Introduzido no ES2017, ele permite escrever código assíncrono de forma síncrona, melhorando a legibilidade e a manutenção. Você aprenderá como declarar funções assíncronas, usar await para aguardar promises, tratar erros com try/catch e decidir quando executar operações em sequência ou em paralelo.

O async/await é construído sobre promises, mas elimina a necessidade de encadeamento com .then() e .catch(). Uma função marcada com async sempre retorna uma promise, e o await pausa a execução até que a promise seja resolvida.

Sintaxe

Para usar async/await, você declara uma função com a palavra-chave async antes de function. Dentro dela, use await antes de uma promise para esperar sua resolução. O valor resolvido da promise é retornado diretamente.

Exemplo básico:

function esperar(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function saudacao() {
  console.log('Olá');
  await esperar(1000);
  console.log('Mundo!');
}

saudacao();
// Saída: Olá (após 1 segundo) Mundo!

Note que await só pode ser usado dentro de funções async. No módulo ES, você pode usar await no topo de módulos (top-level await).

Tratamento de erro com try/catch

Erros em funções assíncronas podem ser capturados com try/catch, similar ao código síncrono. Se uma promise for rejeitada dentro do bloco try, o controle vai para o catch.

Exemplo:

async function buscarDados() {
  try {
    const resposta = await fetch('https://api.exemplo.com/dados');
    if (!resposta.ok) {
      throw new Error('Erro na requisição: ' + resposta.status);
    }
    const dados = await resposta.json();
    console.log(dados);
  } catch (erro) {
    console.error('Erro capturado:', erro.message);
  }
}

buscarDados();

Você também pode usar finally para executar código independentemente de sucesso ou erro, como limpar recursos.

Sequencial vs paralelo

Quando você usa await em cada promise, a execução é sequencial: a próxima operação só começa após a anterior terminar. Isso é útil quando há dependência entre elas.

Exemplo sequencial:

async function sequencial() {
  const a = await Promise.resolve(10);
  const b = await Promise.resolve(20);
  return a + b;
}
// Demora o tempo da soma das duas promises

Para executar em paralelo, use Promise.all (ou Promise.allSettled, Promise.race) para iniciar várias promises simultaneamente e aguardar todas com um único await.

Exemplo paralelo:

async function paralelo() {
  const [a, b] = await Promise.all([
    Promise.resolve(10),
    Promise.resolve(20)
  ]);
  return a + b;
}
// Demora apenas o tempo da promise mais lenta

Escolha entre sequencial e paralelo com base na lógica do seu programa. Se as operações são independentes, o paralelo é mais eficiente.

Boas práticas

Evite usar await dentro de loops forEach; prefira for...of com await ou Promise.all com map. Sempre trate erros em funções assíncronas, seja com try/catch ou encadeando .catch(). Lembre-se de que funções async sempre retornam uma promise, então você pode usar then/catch externamente se necessário.

Referências

Exercícios

  1. Crie uma função assíncrona chamada obterUsuario que recebe um id e retorna um objeto usuário simulado após 1 segundo. Use setTimeout com promise. Em seguida, chame a função e exiba o resultado no console.

    ✓ Resposta:
    function delay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    async function obterUsuario(id) {
      await delay(1000);
      return { id, nome: 'João', email: 'joao@exemplo.com' };
    }
    
    obterUsuario(1).then(console.log);
    // ou (async () => console.log(await obterUsuario(1)))();
  2. Escreva uma função assíncrona dividir que recebe dois números e retorna a divisão, mas rejeita a promise se o divisor for zero. Use try/catch para capturar o erro e exibir uma mensagem amigável.

    ✓ Resposta:
    async function dividir(a, b) {
      if (b === 0) {
        throw new Error('Divisão por zero');
      }
      return a / b;
    }
    
    (async () => {
      try {
        const resultado = await dividir(10, 2);
        console.log(resultado); // 5
        const erro = await dividir(10, 0);
      } catch (erro) {
        console.error('Erro:', erro.message);
      }
    })();
  3. Você tem duas funções assíncronas: tarefaA que leva 2 segundos e tarefaB que leva 3 segundos. Escreva código para executá-las em paralelo e exibir o tempo total aproximado (use console.time e console.timeEnd).

    ✓ Resposta:
    function delay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }
    
    async function tarefaA() {
      await delay(2000);
      return 'A';
    }
    
    async function tarefaB() {
      await delay(3000);
      return 'B';
    }
    
    async function executar() {
      console.time('paralelo');
      const [resultadoA, resultadoB] = await Promise.all([tarefaA(), tarefaB()]);
      console.timeEnd('paralelo'); // ~3 segundos
      console.log(resultadoA, resultadoB);
    }
    
    executar();
  4. Converta o seguinte código baseado em promises encadeadas para usar async/await com try/catch:

    function buscarDados() {
      return fetch('https://api.exemplo.com/dados')
        .then(resposta => resposta.json())
        .then(dados => console.log(dados))
        .catch(erro => console.error(erro));
    }

    ✓ Resposta:
    async function buscarDados() {
      try {
        const resposta = await fetch('https://api.exemplo.com/dados');
        const dados = await resposta.json();
        console.log(dados);
      } catch (erro) {
        console.error(erro);
      }
    }
  5. Escreva uma função assíncrona processarItens que recebe um array de URLs e faz o download de cada uma em paralelo, mas com um limite de 2 requisições simultâneas. Use Promise.all em lotes. Dica: você pode usar um loop ou uma biblioteca, mas tente implementar manualmente.

    ✓ Resposta:
    async function processarItens(urls) {
      const limite = 2;
      const resultados = [];
      for (let i = 0; i < urls.length; i += limite) {
        const lote = urls.slice(i, i + limite);
        const promises = lote.map(url => fetch(url).then(res => res.json()));
        const dados = await Promise.all(promises);
        resultados.push(...dados);
      }
      return resultados;
    }
    
    // Exemplo de uso:
    // const urls = ['url1', 'url2', 'url3', 'url4'];
    // processarItens(urls).then(console.log);