Promise em JavaScript: then, catch e finally
Os três estados de uma Promise, por que then sempre devolve outra Promise e o return esquecido que quebra a cadeia — com cada saída rodada no Node 24.
Uma Promise é um objeto que representa um valor que ainda não existe. Ela é devolvida na hora, vazia, e é preenchida depois — com o resultado, se der certo, ou com o motivo, se der errado.
Você faz três coisas com ela: then para o caminho feliz, catch para o erro,
finally para o que precisa acontecer nos dois casos. A parte que confunde é
que cada um desses três devolve uma nova Promise, e é isso que permite
encadear.
O protocolo de retirada de um pedido que ainda não ficou pronto
Quando você pede uma roupa na lavanderia, recebe um protocolo antes de receber a roupa. O papel não contém o resultado; ele representa a entrega futura e permite combinar o que fazer quando ficar pronta ou quando houver um problema. A roupa pode chegar depois, mas o protocolo existe agora.
Uma Promise é esse representante de um resultado futuro. Ela começa
pending, passa uma vez para fulfilled ou rejected e cada then, catch
ou finally devolve outro protocolo — outra Promise. No primeiro exemplo,
anote a ordem em que os logs deveriam aparecer antes de rodar; a saída mostra
que receber a Promise e receber o valor são momentos diferentes.
new Promise recebe uma função — o executor — com dois parâmetros:
resolve e reject. O executor roda imediatamente; quem espera é quem chamou.
function consultarFrete(cep) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (!/^\d{8}$/.test(cep)) {
reject(new Error(`CEP inválido: ${cep}`));
return;
}
resolve({ cep, prazo: 4, valor: 24.9 });
}, 300);
});
}
const promessa = consultarFrete('01310100');
console.log(promessa);
promessa.then((frete) => {
console.log('chegou:', frete);
console.log(promessa);
});O mesmo objeto, impresso duas vezes: primeiro <pending>, depois com o
valor dentro. Foi só o estado que mudou — a Promise nunca foi substituída.
Com um CEP inválido, o reject leva o fluxo para o catch:
consultarFrete('0131')
.then((frete) => console.log('frete:', frete.valor))
.catch((e) => console.log('recusado:', e.message));Repare no return logo depois do reject, no primeiro exemplo. Ele não é
decoração: sem ele, o executor continuaria rodando e chamaria o resolve
também.
Três estados, e nenhuma volta
Uma Promise é pending até virar fulfilled (resolvida) ou rejected (rejeitada). Essa transição acontece uma vez só, e é irreversível. Tentar mudar de novo não dá erro, não dá aviso: simplesmente não acontece.
const pagamento = new Promise((resolve, reject) => {
resolve('aprovado');
reject(new Error('cartão recusado'));
resolve('aprovado de novo');
console.log('o executor continua rodando depois do resolve');
});
pagamento
.then((status) => console.log('then:', status))
.catch((e) => console.log('catch:', e.message));Duas leituras importantes na saída. O reject foi ignorado: a Promise já estava
resolvida. E o console.log do executor rodou antes do then — o corpo do
executor é síncrono; só o que você pendura com then é que vai para a fila de
microtasks descrita em
JavaScript assíncrono.
then devolve uma Promise nova
Cada then cria um elo. O valor devolvido dentro dele vira o valor recebido
pelo próximo — e um then sem return entrega undefined adiante:
const buscarPedido = (id) => Promise.resolve({ id, cliente: 'Ana', itens: 3, total: 479.7 });
buscarPedido(8241)
.then((pedido) => {
console.log('1 —', pedido.cliente, 'R$', pedido.total);
return pedido.total;
})
.then((total) => {
console.log('2 — com frete:', total + 24.9);
return total + 24.9;
})
.then((total) => {
console.log('3 — em centavos:', Math.round(total * 100));
})
.then((valor) => {
console.log('4 — o then anterior não devolveu nada:', valor);
});O 504.59999999999997 é ponto flutuante, não bug da cadeia — o mesmo assunto
que aparece em variáveis em JavaScript quando se
soma dinheiro. Por isso a linha seguinte converte para centavos.
O return que falta quebra tudo
Este é o erro número um de quem está aprendendo Promise. A cadeia parece certa,
o encadeamento parece certo, e o segundo then recebe undefined:
const buscarCliente = (id) =>
new Promise((resolve) => setTimeout(() => resolve({ id, nome: 'Ana', cep: '01310100' }), 200));
const consultarFrete = (cep) =>
new Promise((resolve) => setTimeout(() => resolve({ cep, valor: 24.9 }), 200));
buscarCliente(31)
.then((cliente) => {
consultarFrete(cliente.cep); // faltou o return
})
.then((frete) => {
console.log('frete:', frete);
console.log('valor:', frete.valor);
});A consulta de frete até aconteceu — mas ninguém devolveu a Promise dela, então a
cadeia não esperou e seguiu com undefined. Com o return, a cadeia passa a
aguardar:
buscarCliente(31)
.then((cliente) => consultarFrete(cliente.cep))
.then((frete) => {
console.log('frete:', frete);
console.log('valor:', frete.valor);
});catch pega o que estourou em qualquer elo
Um throw dentro de um then rejeita a Promise daquele elo. O fluxo pula todos
os then seguintes e cai no primeiro catch:
Promise.resolve({ id: 8241, total: 479.7 })
.then((pedido) => {
if (pedido.total > 100) throw new Error('pedido acima do limite sem aprovação');
return pedido;
})
.then((pedido) => console.log('nunca chega aqui', pedido.id))
.catch((e) => console.log('catch pegou:', e.message))
.then(() => console.log('e a cadeia volta ao normal'));A última linha é a parte que surpreende: depois do catch, a cadeia volta a
ser bem-sucedida. O catch é o try/catch do mundo assíncrono — tratou, o
programa continua. Se você quer que a falha continue subindo, precisa lançar de
novo dentro do próprio catch.
finally roda nos dois desfechos
Serve para limpeza: desligar o spinner, fechar a conexão, liberar o botão.
function cobrar(cartao, valor) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (cartao.saldo < valor) {
reject(new Error('saldo insuficiente'));
return;
}
resolve({ autorizacao: 'A7X91', valor });
}, 200);
});
}
let carregando = true;
cobrar({ saldo: 100 }, 479.7)
.then((recibo) => console.log('pago:', recibo.autorizacao))
.catch((e) => console.log('falhou:', e.message))
.finally(() => {
carregando = false;
console.log('carregando =', carregando);
})
.then(() => console.log('a cadeia continua depois do catch'));finally não recebe argumento e não altera o valor que passa por ele. É de
propósito: ele não é um elo de transformação, é um elo de limpeza.
Rejeição sem catch derruba o processo
Uma Promise rejeitada que ninguém trata não fica quieta. No Node ela encerra o
programa; no navegador, aparece como Uncaught (in promise) no console.
function cobrar(valor) {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('cartão recusado pela operadora')), 200);
});
}
cobrar(479.7).then((recibo) => console.log('pago:', recibo));
console.log('pedido registrado');Repare que pedido registrado foi impresso e o processo morreu 200ms depois,
longe da linha que “causou” o problema. Por isso a regra: toda cadeia termina
em catch — ou é devolvida para alguém que vai encadear um.
Quando ainda vale escrever new Promise
Quase nunca. Se a função que você chama já devolve Promise — fetch, as APIs de
node:fs/promises, qualquer driver moderno de banco — envolver aquilo em
new Promise só adiciona um lugar a mais para errar. Este é o antipadrão mais
comum:
// não faça isso
function buscarProdutos() {
return new Promise((resolve, reject) => {
fetch('http://localhost:4599/produtos')
.then((r) => r.json())
.then(resolve)
.catch(reject);
});
}
// faça isso
function buscarProdutosOk() {
return fetch('http://localhost:4599/produtos').then((r) => r.json());
}new Promise se justifica em dois casos: embrulhar uma API antiga de callback
(fs.readFile no estilo (erro, dado)) e transformar tempo em valor, como um
espera(ms) para retry com intervalo. Fora disso, componha as Promises que já
existem.
A próxima lição pega exatamente estas cadeias e reescreve com async e await, que deixa o código com cara de síncrono sem mudar nada do que você acabou de ver. Depois vem Promise.all e companhia, para disparar várias de uma vez. O caminho completo está no guia de JavaScript.
Perguntas frequentes
Preciso aprender Promise se já existe async/await?
Qual a diferença entre then com dois argumentos e then seguido de catch?
O que acontece se eu chamar resolve duas vezes?
finally recebe o valor da Promise?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- MDN — Usando Promises — developer.mozilla.org
- MDN — Promise — developer.mozilla.org


