Programação Assíncrona em JavaScript: Promessas e Funções Assíncronas

Modelo Assíncrono no JavaScript

O JavaScript opera em um ambiente single-threaded com ciclo de eventos, permitindo que operações assíncronas sejam executadas sem bloquear o fluxo principal. Antes do ES6, a abordagem padrão era o uso de callbacks aninhados, levando ao problema conhecido como "callback hell". Com a introdução das Promise (ES6) e da sintaxe async/await (ES2017), o código assíncrono tornou-se mais legível e gerenciável.

Promessas (Promises)

Conceitos Fundamentais

Uma Promise representa o resultado final de uma operação assíncrona — bem-sucedida ou falhada. Ela possui três estados imutáveis:

  • pending: estado inicial.
  • fulfilled: operação concluída com sucesso.
  • rejected: operação falhou.

Uma Promise é criada com o construtor new Promise((resolve, reject) => { ... }). Chamar resolve(valor) altera o estado para fulfilled, enquanto reject(motivo) ou um erro lançado dentro do bloco fazem o mesmo.

const task = new Promise((resolve, reject) => {
  setTimeout(() => resolve('Processamento concluído'), 2000);
});

Métodos de Instância

  • then(onFulfilled, onRejected): registra funções a serem chamadas quando a promessa for resolvida ou rejeitada. Retorna uma nova Promise, habilitando encadeamento.
  • catch(onRejected): atalho para then(null, onRejected), usado para tratar erros.
  • finally(onFinally): executa sempre, independentemente do resultado. Útil para limpeza de recursos.
task
  .then(result => console.log(result))
  .catch(error => console.error('Erro:', error))
  .finally(() => console.log('Finalizado'));

Encadeamento e Propagação de Valores

Cada chamada a then ou catch retorna uma nova Promise. O valor retornado por um manipulador é passado como entrada para o próximo then. Se o retorno for outra Promise, a cadeia aguarda sua conclusão antes de continuar.

Métodos Estáticos

Método Descrição
Promise.resolve(value) Transforma um valor em uma Promise resolvida. Expande objetos thenable.
Promise.reject(reason) Cria uma Promise rejeitada.
Promise.all(iterable) Resolve apenas quando todas as Promises forem bem-sucedidas. Primeiro erro causa rejeição global.
Promise.allSettled(iterable) Aguarda todas as Promises terminarem, retornando detalhes de cada uma (sucesso ou falha).
Promise.race(iterable) Retorna o resultado da primeira Promise a concluir, independentemente do sucesso ou falha.
Promise.any(iterable) Retorna a primeira Promise bem-sucedida. Rejeita apenas se todas falharem.
Promise.all([fetch('/api/users'), fetch('/api/posts')])
  .then(([users, posts]) => {
    console.log('Usuários:', users.json());
    console.log('Posts:', posts.json());
  })
  .catch(err => console.error('Falha em alguma requisição:', err));

Tratamento de Erros

Se uma Promise for rejeitada sem tratamento, um evento unhandledrejection será disparado. É recomendável sempre usar catch ou fornecer um segundo argumento em then.

Funções Assíncronas (async/await)

Palavra-chave async

Funções declaradas com async sempre retornam uma Promise. O valor retornado é envolvido por Promise.resolve(). Caso ocorra uma exceção, a função retorna uma Promise rejeitada.

async function obterDados() {
  return 'Resposta';
}
obterDados().then(console.log); // Imprime: Resposta

Operador await

Usado apenas dentro de funções async. Pausa a execução até que a Promise seja resolvida ou rejeitada. Em caso de sucesso, retorna o valor resolvido; em caso de falha, lança uma exceção capturável com try/catch.

async function buscarRecursos() {
  try {
    const response = await fetch('/dados');
    const dados = await response.json();
    console.log(dados);
  } catch (erro) {
    console.error('Falha na requisição:', erro);
  }
}

Relação com Promessas

async/await é uma camada de abstração sobre Promessas, oferecendo sintaxe mais linear e natural. Internamente, ele traduz o código em encadeamentos de then.

Manuseio de Erros

Erros em await devem ser tratados com try/catch. Sem tratamento, a função async retorna uma Promise rejeitada, que pode ser capturada no nível superior.

Execução Concorrente

Evite chamadas sequenciais com await quando os processos não dependem uns dos outros. Use Promise.all para executar em paralelo.

// Ineficiente: execução sequencial
const dado1 = await carregarTarefa1();
const dado2 = await carregarTarefa2();

// Correto: execução simultânea
const [dado1, dado2] = await Promise.all([
  carregarTarefa1(),
  carregarTarefa2()
]);

Padrões Recorrentes e Boas Práticas

Execução Série vs Paralela

Use Promise.all ou Promise.allSettled para tarefas concorrentes. Para dependências, use encadeamento com then.

Controle de Tempo Limite

Implemente timeouts personalizados para evitar espera infinita.

function tempoLimite(promise, duracao) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Tempo esgotado')), duracao)
  );
  return Promise.race([promise, timeout]);
}

tempoLimite(fetch('/api'), 5000)
  .then(res => console.log(res))
  .catch(err => console.error(err));

Debouncing e Throttling com Assíncrono

Use flags ou controladores de cancelamento para evitar múltiplas requisições em rápida sucessão.

Perguntas Frequentes em Entrevistas

  1. Quais são os estados de uma Promise? → pending, fulfilled, rejected (imutáveis).
  2. O que retorna then? → Uma nova Promise, permitindo encadeamento.
  3. Como executar várias Promises em paralelo com sucesso garantido? → Use Promise.all.
  4. Diferença entre all e allSettled? → all falha com a primeira rejeição; allSettled coleta todos os resultados.
  5. Como diferenciar race e any? → race retorna a primeira conclusão; any ignora falhas e só rejeita se todas falharem.
  6. Principais vantagens do async/await? → Código mais claro, tratamento de erros com try/catch, melhor legibilidade.
  7. Como fazer requisições concorrentes em async? → Use Promise.all para iniciar e await no resultado.
  8. O que é top-level await? → Permite await diretamente em módulos ES2022, fazendo o módulo esperar a conclusão da Promise.
  9. É possível cancelar uma Promise? → Não diretamente, mas com AbortController em combinação com fetch ou APIs similares.
  10. O que acontece com Promises rejeitadas não tratadas? → Dispara o evento unhandledrejection no console. Sempre inclua catch.

Tags: javascript promise Async/Await assincrono promises

Publicado em 10-10 23:24