Dominando Promises e Async/Await em JavaScript

Publicado em 2026-02-01 • leitura estimada • ~5 min

Entenda de vez programação assíncrona: callbacks, Promises, async/await, tratamento de erros e padrões avançados com exemplos práticos.

Por que JavaScript é assíncrono

JavaScript é single-threaded (uma thread de execução). Para evitar bloquear a UI ou o servidor durante operações demoradas (requisições HTTP, leitura de arquivos, timers), JavaScript usa programação assíncrona.

Operações assíncronas comuns

  • Requisições HTTP (fetch, axios)
  • Leitura/escrita de arquivos (Node.js)
  • Timers (setTimeout, setInterval)
  • Acesso a bancos de dados
  • WebSockets e eventos

Evolução: Callbacks → Promises → Async/Await

1. Callbacks (o jeito antigo)

Funções passadas como argumento para serem executadas quando a operação termina.

// Exemplo básico
setTimeout(() => {
  console.log('Executou após 1 segundo');
}, 1000);

// Problema: Callback Hell (pirâmide da morte)
getUser(userId, (error, user) => {
  if (error) {
    console.error(error);
    return;
  }
  getPosts(user.id, (error, posts) => {
    if (error) {
      console.error(error);
      return;
    }
    getComments(posts[0].id, (error, comments) => {
      if (error) {
        console.error(error);
        return;
      }
      console.log(comments);
    });
  });
});

2. Promises (ES6/2015)

Promises representam um valor que estará disponível no futuro e têm 3 estados:

  • Pending: operação ainda não terminou
  • Fulfilled: operação completou com sucesso (resolve)
  • Rejected: operação falhou (reject)

Criando uma Promise

const minhaPromise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const sucesso = true;
    if (sucesso) {
      resolve('Operação completada!');
    } else {
      reject('Erro na operação');
    }
  }, 1000);
});

// Consumindo a Promise
minhaPromise
  .then(resultado => console.log(resultado))
  .catch(erro => console.error(erro));

Encadeamento (Promise chaining)

getUser(userId)
  .then(user => getPosts(user.id))
  .then(posts => getComments(posts[0].id))
  .then(comments => console.log(comments))
  .catch(error => console.error(error));

3. Async/Await (ES2017)

Syntactic sugar sobre Promises. Deixa código assíncrono parecer síncrono.

async function buscarDados() {
  try {
    const user = await getUser(userId);
    const posts = await getPosts(user.id);
    const comments = await getComments(posts[0].id);
    console.log(comments);
  } catch (error) {
    console.error(error);
  }
}

buscarDados();

Promises na prática

Exemplo 1: Requisição HTTP com fetch

// Com Promises
fetch('https://api.example.com/users')
  .then(response => {
    if (!response.ok) {
      throw new Error('Erro na requisição');
    }
    return response.json();
  })
  .then(data => console.log(data))
  .catch(error => console.error(error));

Exemplo 2: Simulando operação assíncrona

function buscarUsuario(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const usuarios = {
        1: { id: 1, nome: 'João' },
        2: { id: 2, nome: 'Maria' }
      };

      const usuario = usuarios[id];
      if (usuario) {
        resolve(usuario);
      } else {
        reject(new Error('Usuário não encontrado'));
      }
    }, 1000);
  });
}

// Usar
buscarUsuario(1)
  .then(user => console.log(user))
  .catch(err => console.error(err));

Exemplo 3: Promise.all (executar em paralelo)

const promise1 = fetch('https://api.example.com/users');
const promise2 = fetch('https://api.example.com/posts');
const promise3 = fetch('https://api.example.com/comments');

Promise.all([promise1, promise2, promise3])
  .then(responses => Promise.all(responses.map(r => r.json())))
  .then(([users, posts, comments]) => {
    console.log('Usuários:', users);
    console.log('Posts:', posts);
    console.log('Comentários:', comments);
  })
  .catch(error => console.error('Alguma requisição falhou:', error));

Async/Await na prática

Exemplo 1: Requisição HTTP com async/await

async function buscarUsuarios() {
  try {
    const response = await fetch('https://api.example.com/users');

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const data = await response.json();
    console.log(data);
    return data;
  } catch (error) {
    console.error('Erro ao buscar usuários:', error);
    throw error;
  }
}

buscarUsuarios();

Exemplo 2: Múltiplas operações sequenciais

async function processarPedido(pedidoId) {
  try {
    console.log('Buscando pedido...');
    const pedido = await buscarPedido(pedidoId);

    console.log('Validando estoque...');
    const estoqueOk = await validarEstoque(pedido.items);

    if (!estoqueOk) {
      throw new Error('Estoque insuficiente');
    }

    console.log('Processando pagamento...');
    const pagamento = await processarPagamento(pedido.valor);

    console.log('Enviando email...');
    await enviarEmailConfirmacao(pedido.email);

    console.log('Pedido processado com sucesso!');
    return { pedido, pagamento };
  } catch (error) {
    console.error('Erro ao processar pedido:', error);
    throw error;
  }
}

processarPedido(123);

Exemplo 3: Operações em paralelo com Promise.all

async function buscarDadosCompletos(userId) {
  try {
    // Executa as 3 requisições simultaneamente
    const [user, posts, friends] = await Promise.all([
      fetch(`/api/users/${userId}`).then(r => r.json()),
      fetch(`/api/users/${userId}/posts`).then(r => r.json()),
      fetch(`/api/users/${userId}/friends`).then(r => r.json())
    ]);

    return { user, posts, friends };
  } catch (error) {
    console.error('Erro ao buscar dados:', error);
    throw error;
  }
}

buscarDadosCompletos(1)
  .then(dados => console.log(dados));

Métodos úteis de Promise

Promise.all (todas devem completar)

// Espera todas completarem. Se uma falhar, rejeita tudo.
const results = await Promise.all([
  fetchUsers(),
  fetchPosts(),
  fetchComments()
]);
// results = [users, posts, comments]

Promise.allSettled (espera todas, mesmo com erro)

// Espera todas completarem, mesmo com erros
const results = await Promise.allSettled([
  fetchUsers(),
  fetchPosts(),
  fetchComments()
]);

results.forEach((result, index) => {
  if (result.status === 'fulfilled') {
    console.log(`Promise ${index} sucesso:`, result.value);
  } else {
    console.error(`Promise ${index} falhou:`, result.reason);
  }
});

Promise.race (primeira a completar vence)

// Retorna a primeira Promise a completar (sucesso ou erro)
const fastest = await Promise.race([
  fetch('https://api1.example.com/data'),
  fetch('https://api2.example.com/data'),
  fetch('https://api3.example.com/data')
]);

console.log('API mais rápida respondeu:', fastest);

Promise.any (primeira a dar sucesso)

// Retorna a primeira Promise a dar sucesso (ignora erros)
const result = await Promise.any([
  fetch('https://api1.example.com/data'),
  fetch('https://api2.example.com/data'),
  fetch('https://api3.example.com/data')
]);

console.log('Primeira a dar sucesso:', result);

Tratamento de erros

Com Promises (.catch)

fetch('/api/users')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    console.error('Erro capturado:', error);
    // Tratar erro
  })
  .finally(() => {
    console.log('Sempre executa');
  });

Com async/await (try/catch)

async function buscarDados() {
  try {
    const response = await fetch('/api/users');
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Erro capturado:', error);
    // Tratar erro
  } finally {
    console.log('Sempre executa');
  }
}

Tratamento granular de erros

async function salvarUsuario(userData) {
  try {
    const response = await fetch('/api/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(userData)
    });

    if (response.status === 401) {
      throw new Error('Não autorizado');
    }

    if (response.status === 400) {
      const errorData = await response.json();
      throw new Error(`Validação falhou: ${errorData.message}`);
    }

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    return await response.json();
  } catch (error) {
    if (error.message.includes('Não autorizado')) {
      // Redirecionar para login
      console.error('Redirecionar para login');
    } else if (error.message.includes('Validação')) {
      // Mostrar erros de validação
      console.error('Erros de validação:', error.message);
    } else {
      // Erro genérico
      console.error('Erro ao salvar usuário:', error);
    }
    throw error;
  }
}

Padrões avançados

1. Retry automático

async function fetchComRetry(url, retries = 3, delay = 1000) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error('HTTP error');
      return await response.json();
    } catch (error) {
      if (i === retries - 1) throw error;
      console.log(`Tentativa ${i + 1} falhou. Tentando novamente...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

fetchComRetry('https://api.example.com/data', 3, 2000);

2. Timeout para Promises

function promiseComTimeout(promise, timeout = 5000) {
  return Promise.race([
    promise,
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Timeout')), timeout)
    )
  ]);
}

// Usar
try {
  const data = await promiseComTimeout(
    fetch('https://api.example.com/slow-endpoint'),
    3000
  );
  console.log(data);
} catch (error) {
  console.error('Timeout ou erro:', error);
}

3. Debounce assíncrono (para search inputs)

function debounce(func, delay = 300) {
  let timeoutId;
  return (...args) => {
    clearTimeout(timeoutId);
    return new Promise(resolve => {
      timeoutId = setTimeout(() => resolve(func(...args)), delay);
    });
  };
}

const buscarAPI = debounce(async (query) => {
  const response = await fetch(`/api/search?q=${query}`);
  return await response.json();
}, 500);

// Usar em input de busca
input.addEventListener('input', async (e) => {
  const results = await buscarAPI(e.target.value);
  mostrarResultados(results);
});

4. Executar Promises em lote (batching)

async function processarEmLotes(items, batchSize = 10) {
  const results = [];

  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize);
    const batchResults = await Promise.all(
      batch.map(item => processarItem(item))
    );
    results.push(...batchResults);
    console.log(`Processados ${i + batch.length} de ${items.length}`);
  }

  return results;
}

// Processar 1000 items em lotes de 50
processarEmLotes(items, 50);

5. Cancelando Promises com AbortController

async function fetchComCancelamento() {
  const controller = new AbortController();
  const signal = controller.signal;

  // Cancelar após 5 segundos
  setTimeout(() => controller.abort(), 5000);

  try {
    const response = await fetch('/api/data', { signal });
    return await response.json();
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Requisição cancelada');
    } else {
      throw error;
    }
  }
}

fetchComCancelamento();

Erros comuns e como evitar

1. Esquecer await

// Errado: retorna Promise, não o valor
async function buscar() {
  const data = fetch('/api/users');  // Falta await!
  console.log(data);  // Promise {<pending>}
}

// Correto
async function buscar() {
  const response = await fetch('/api/users');
  const data = await response.json();
  console.log(data);  // Dados reais
}

2. Não tratar erros

// Errado: erro não tratado
async function buscar() {
  const data = await fetch('/api/users');
}

// Correto
async function buscar() {
  try {
    const data = await fetch('/api/users');
  } catch (error) {
    console.error('Erro:', error);
  }
}

3. Await desnecessário em sequência

// Lento: executa em sequência (3 segundos)
async function buscarTudo() {
  const users = await fetchUsers();      // 1s
  const posts = await fetchPosts();      // 1s
  const comments = await fetchComments(); // 1s
}

// Rápido: executa em paralelo (1 segundo)
async function buscarTudo() {
  const [users, posts, comments] = await Promise.all([
    fetchUsers(),
    fetchPosts(),
    fetchComments()
  ]);
}

Boas práticas

  • Sempre trate erros: use try/catch ou .catch()
  • Use Promise.all para paralelizar: quando operações são independentes
  • Evite await desnecessário: se não usa o resultado imediatamente
  • Prefira async/await sobre Promises: código mais legível
  • Não misture callbacks com Promises: escolha um padrão

Conclusão

Promises e async/await tornaram JavaScript assíncrono muito mais legível e manutenível. Comece entendendo Promises e, depois, migre para async/await. Use Promise.all para paralelizar, trate erros com try/catch e conheça os métodos úteis (allSettled, race, any). Com esses conceitos, você dominará programação assíncrona em JavaScript.

FAQ

Async/await é mais rápido que Promises?

Não. São equivalentes em performance. Async/await é apenas syntactic sugar sobre Promises.

Posso usar await fora de funções async?

Apenas em top-level await (módulos ES6). Dentro de funções, precisa ser async.

Como cancelar uma Promise?

Use AbortController com fetch ou implemente lógica de cancelamento manual. Promises não têm cancelamento nativo.

O que acontece se eu não usar await?

A função retorna uma Promise pendente, não o valor resolvido. Você precisa usar .then() ou await.

Posso usar async/await no navegador?

Sim, todos os navegadores modernos suportam (Chrome 55+, Firefox 52+, Safari 10.1+). Para navegadores antigos, use Babel.