Promises são objetos que representam a eventual conclusão (ou falha) de uma operação assíncrona e seu valor resultante. Elas fornecem uma maneira mais limpa e legível de lidar com código assíncrono, evitando o famoso "callback hell". Uma Promise pode estar em um de três estados: pendente (pending), resolvida (fulfilled) ou rejeitada (rejected).

Nesta aula, vamos cobrir os conceitos essenciais de Promises, incluindo seus estados, métodos de tratamento (then, catch, finally), como encadear múltiplas Promises e como criar suas próprias Promises a partir do zero.

Estados

Uma Promise possui três estados mutuamente exclusivos:

  • Pending (pendente): estado inicial, a Promise ainda não foi resolvida nem rejeitada.
  • Fulfilled (resolvida): a operação assíncrona foi concluída com sucesso, e a Promise possui um valor.
  • Rejected (rejeitada): a operação assíncrona falhou, e a Promise possui uma razão (erro).

Uma vez que uma Promise é resolvida ou rejeitada, ela se torna imutável: seu estado e valor não podem ser alterados. Isso garante previsibilidade no fluxo assíncrono.

Exemplo visualizando estados:

const promise = new Promise((resolve, reject) => {
  // estado pending
  setTimeout(() => {
    resolve('Sucesso!'); // muda para fulfilled
    // reject('Erro');    // mudaria para rejected
  }, 1000);
});
console.log(promise); // Promise { <pending> }

then/catch/finally

Os métodos then, catch e finally são usados para reagir ao resultado de uma Promise.

then(onFulfilled, onRejected): Retorna uma nova Promise. O primeiro callback é executado quando a Promise é resolvida, o segundo (opcional) quando é rejeitada.

const promise = Promise.resolve(42);
promise.then(
  value => console.log('Resolvida com', value),
  reason => console.log('Rejeitada por', reason)
);
// Saída: Resolvida com 42

catch(onRejected): É uma abreviação para .then(undefined, onRejected). Captura rejeições que ocorreram em qualquer ponto anterior da cadeia.

Promise.reject('Erro')
  .catch(error => console.log('Capturado:', error));
// Saída: Capturado: Erro

finally(onFinally): Executa um callback independentemente do resultado (resolvida ou rejeitada). Útil para limpeza, como fechar conexões. Não recebe argumentos e não altera o valor da Promise.

Promise.resolve('OK')
  .finally(() => console.log('Finalizado'))
  .then(value => console.log('Valor:', value));
// Saída: Finalizado
//        Valor: OK

Encadeamento

O encadeamento de Promises permite executar operações assíncronas sequenciais de forma linear. Cada then retorna uma nova Promise, que pode ser usada para o próximo then.

Exemplo: buscar dados de uma API e processá-los.

fetch('https://api.exemplo.com/dados')
  .then(response => response.json())
  .then(data => {
    console.log('Dados recebidos:', data);
    return data.id;
  })
  .then(id => fetch(`https://api.exemplo.com/detalhes/${id}`))
  .then(response => response.json())
  .then(detalhes => console.log('Detalhes:', detalhes))
  .catch(error => console.error('Erro na cadeia:', error));

Se qualquer Promise na cadeia for rejeitada, o controle pula para o catch mais próximo. É uma boa prática ter um catch no final da cadeia para tratar erros.

Criando uma Promise

Podemos criar nossas próprias Promises usando o construtor Promise, que recebe uma função executor com dois parâmetros: resolve e reject.

const minhaPromise = new Promise((resolve, reject) => {
  // Operação assíncrona: setTimeout, leitura de arquivo, etc.
  const sucesso = true;
  if (sucesso) {
    resolve('Operação bem-sucedida');
  } else {
    reject('Falha na operação');
  }
});

minhaPromise
  .then(resultado => console.log(resultado))
  .catch(erro => console.error(erro));

É comum usar Promises para envolver funções que usam callbacks, como setTimeout ou APIs legadas.

function esperar(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
esperar(2000).then(() => console.log('Passaram-se 2 segundos'));

Importante: a função executor é executada imediatamente (sincronamente) quando a Promise é criada, mas o resolve ou reject são chamados assincronamente.

Boas Práticas

  • Sempre retorne Promises em encadeamentos para manter a sequência.
  • Adicione um catch ao final de cada cadeia para evitar erros não tratados.
  • Use finally para operações de limpeza (ex.: fechar arquivos, esconder loading).
  • Prefira Promise.all para operações paralelas independentes.

Referências

Exercícios

  1. Crie uma Promise que seja resolvida após 1 segundo com o valor "Pronto". Use then para exibir o valor no console.
  2. ✓ Resposta:
    const promise = new Promise(resolve => {
      setTimeout(() => resolve('Pronto'), 1000);
    });
    promise.then(value => console.log(value));
  3. Escreva uma função dividir(a, b) que retorna uma Promise. Se b for 0, rejeite com "Divisão por zero"; caso contrário, resolva com a/b.
  4. ✓ Resposta:
    function dividir(a, b) {
      return new Promise((resolve, reject) => {
        if (b === 0) {
          reject('Divisão por zero');
        } else {
          resolve(a / b);
        }
      });
    }
    dividir(10, 2).then(console.log).catch(console.error); // 5
    dividir(10, 0).then(console.log).catch(console.error); // 'Divisão por zero'
  5. Encadeie duas Promises: a primeira resolve com 5 após 1 segundo, a segunda multiplica o valor por 2. Exiba o resultado final.
  6. ✓ Resposta:
    new Promise(resolve => setTimeout(() => resolve(5), 1000))
      .then(valor => valor * 2)
      .then(resultado => console.log(resultado)); // 10 após 1 segundo
  7. Use finally para imprimir "Operação finalizada" independentemente de sucesso ou erro em uma Promise que rejeita com "Erro simulado".
  8. ✓ Resposta:
    Promise.reject('Erro simulado')
      .finally(() => console.log('Operação finalizada'))
      .catch(error => console.error(error));
    // Saída: Operação finalizada
    //        Erro simulado
  9. Crie uma função buscarUsuario(id) que retorna uma Promise que simula uma requisição: se id for par, resolve com {id, nome: "Usuário"+id}; se ímpar, rejeita com "Usuário não encontrado".
  10. ✓ Resposta:
    function buscarUsuario(id) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          if (id % 2 === 0) {
            resolve({ id, nome: `Usuário${id}` });
          } else {
            reject('Usuário não encontrado');
          }
        }, 500);
      });
    }
    buscarUsuario(2).then(console.log).catch(console.error); // { id: 2, nome: 'Usuário2' }
    buscarUsuario(1).then(console.log).catch(console.error); // 'Usuário não encontrado'