No artigo Callbacks: o começo de tudo vimos o Callback Hell — código que cresce horizontalmente, difícil de ler e manter. As Promises foram introduzidas no ES6 (2015) como a solução oficial do JavaScript para esse problema.
Uma Promise representa um valor que ainda não está disponível, mas que estará em algum momento no futuro — ou que pode falhar. Em vez de passar um callback para dentro de uma função, você recebe um objeto que representa a operação em andamento e encadeia o que fazer com o resultado.
O conceito
Pense em uma Promise como um recibo de pedido num restaurante. Você faz o pedido (inicia a operação), recebe um número (a Promise), e continua fazendo outras coisas. Quando o pedido fica pronto, o garçom te avisa (resolve). Se algo deu errado na cozinha, você também é avisado (reject).
// Uma Promise tem sempre um de três estados:
// pending → aguardando (operação em andamento)
// fulfilled → concluída com sucesso (resolve foi chamado)
// rejected → falhou (reject foi chamado)
Criando uma Promise
const minhaPromise = new Promise(function(resolve, reject) {
// O executor — executa imediatamente
const sucesso = true;
if (sucesso) {
resolve("Operação concluída com sucesso!"); // a promise passa a fulfilled
} else {
reject(new Error("Algo deu errado.")); // a promise passa a rejected
}
});
O construtor recebe uma função chamada executor, que recebe dois callbacks:
resolve(valor)— chame quando a operação tiver sucessoreject(erro)— chame quando a operação falhar
Consumindo com .then() e .catch()
minhaPromise
.then(function(resultado) {
// Executado se a promise foi resolvida
console.log(resultado); // "Operação concluída com sucesso!"
})
.catch(function(erro) {
// Executado se a promise foi rejeitada
console.error(erro.message);
});
Com arrow functions — como você verá na prática:
minhaPromise
.then(resultado => console.log(resultado))
.catch(erro => console.error(erro.message));
Promise assíncrona — o caso real
function buscarUsuario(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id <= 0) {
reject(new Error("ID inválido."));
return;
}
resolve({
id,
nome: "Ana Paula",
email: "ana@email.com",
plano: "premium",
});
}, 1000);
});
}
// Usando a função
buscarUsuario(42)
.then(usuario => {
console.log(`Olá, ${usuario.nome}!`);
console.log(`Plano: ${usuario.plano}`);
})
.catch(erro => {
console.error(`Erro: ${erro.message}`);
});
buscarUsuario(-1)
.then(usuario => console.log(usuario.nome))
.catch(erro => console.error(`Erro: ${erro.message}`)); // Erro: ID inválido.
Compare com a versão em callback do artigo Callbacks: o começo de tudo — muito mais limpo.
Encadeamento de .then()
A grande vantagem das Promises: você pode encadear operações dependentes de forma linear, sem aninhar:
function buscarUsuario(id) {
return new Promise(resolve => {
setTimeout(() => resolve({ id, nome: "Carlos", enderecoId: 7 }), 500);
});
}
function buscarEndereco(enderecoId) {
return new Promise(resolve => {
setTimeout(() => resolve({ id: enderecoId, cidade: "Curitiba", rua: "Av. Brasil" }), 400);
});
}
function buscarPedidos(usuarioId) {
return new Promise(resolve => {
setTimeout(() => resolve([{ id: 101, total: 250 }, { id: 102, total: 180 }]), 600);
});
}
// Encadeamento linear — sem pirâmide!
buscarUsuario(1)
.then(usuario => {
console.log(`Usuário: ${usuario.nome}`);
return buscarEndereco(usuario.enderecoId); // retorna uma nova Promise
})
.then(endereco => {
console.log(`Cidade: ${endereco.cidade}`);
return buscarPedidos(1); // retorna uma nova Promise
})
.then(pedidos => {
console.log(`Pedidos: ${pedidos.length}`);
const total = pedidos.reduce((acc, p) => acc + p.total, 0);
console.log(`Total: R$ ${total}`);
})
.catch(erro => {
// Um único catch trata erros de QUALQUER etapa da cadeia
console.error(`Algo deu errado: ${erro.message}`);
});
Antes eram 4 níveis aninhados de callbacks. Agora é uma cadeia linear e legível. O catch no final captura qualquer erro de qualquer etapa.
Como o encadeamento funciona
Cada .then() retorna uma nova Promise. O valor retornado dentro do .then() se torna o valor resolvido da próxima Promise na cadeia:
Promise.resolve(1)
.then(valor => {
console.log(valor); // 1
return valor + 1; // retorna 2
})
.then(valor => {
console.log(valor); // 2
return valor * 3; // retorna 6
})
.then(valor => {
console.log(valor); // 6
});
Se você retornar uma Promise dentro do .then(), o encadeamento espera essa Promise resolver antes de chamar o próximo .then().
.finally() — executar sempre
Assim como o finally do try/catch, ele executa independente de sucesso ou falha:
function carregarDados() {
mostrarLoading(true);
return buscarUsuario(1)
.then(usuario => {
exibirUsuario(usuario);
})
.catch(erro => {
exibirErro(erro.message);
})
.finally(() => {
mostrarLoading(false); // sempre oculta o loading
});
}
Promise.resolve() e Promise.reject()
Atalhos para criar Promises já resolvidas ou rejeitadas:
// Já resolvida
Promise.resolve("valor imediato")
.then(v => console.log(v)); // "valor imediato"
// Já rejeitada
Promise.reject(new Error("falha imediata"))
.catch(e => console.error(e.message)); // "falha imediata"
// Útil para tornar funções síncronas compatíveis com código assíncrono
function obterConfiguracao(chave) {
const cache = { tema: "escuro", idioma: "pt-BR" };
if (cache[chave]) {
return Promise.resolve(cache[chave]); // retorna Promise mesmo sendo síncrono
}
return buscarConfiguracaoDoServidor(chave); // assíncrono real
}
Promise.all() — executar em paralelo
Executa múltiplas Promises ao mesmo tempo e aguarda todas terminarem:
const promessas = [
buscarUsuario(1),
buscarUsuario(2),
buscarUsuario(3),
];
Promise.all(promessas)
.then(usuarios => {
// usuarios é um array com os resultados na mesma ordem
console.log(`${usuarios.length} usuários carregados`);
usuarios.forEach(u => console.log(`- ${u.nome}`));
})
.catch(erro => {
// Se QUALQUER uma falhar, cai aqui
console.error(`Falha: ${erro.message}`);
});
Promise.all é essencial para performance — em vez de fazer 3 requisições em sequência (3 segundos), faz as 3 ao mesmo tempo (~1 segundo, o tempo da mais lenta).
Promise.allSettled() — quando quer todos os resultados
Diferente do Promise.all, o allSettled não falha se uma Promise rejeitar — retorna o resultado de todas, sejam sucessos ou falhas:
const promises = [
buscarUsuario(1), // vai funcionar
buscarUsuario(-1), // vai rejeitar
buscarUsuario(3), // vai funcionar
];
Promise.allSettled(promises)
.then(resultados => {
resultados.forEach((resultado, i) => {
if (resultado.status === "fulfilled") {
console.log(`✅ Usuário ${i + 1}: ${resultado.value.nome}`);
} else {
console.log(`❌ Usuário ${i + 1}: ${resultado.reason.message}`);
}
});
});
// ✅ Usuário 1: Ana Paula
// ❌ Usuário 2: ID inválido.
// ✅ Usuário 3: Ana Paula
Promise.race() — o mais rápido vence
Resolve ou rejeita assim que a primeira Promise terminar:
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timeout: requisição muito lenta.")), 3000)
);
const requisicao = buscarUsuario(1);
Promise.race([requisicao, timeout])
.then(usuario => console.log(`Carregado: ${usuario.nome}`))
.catch(erro => console.error(erro.message));
// Se buscarUsuario demorar mais de 3s → "Timeout: requisição muito lenta."
// Se terminar antes → exibe o usuário
Isso é um padrão muito útil para implementar timeout em requisições.
Promise.any() — o primeiro sucesso
Resolve com o primeiro que tiver sucesso. Só rejeita se todos falharem:
// Tentar múltiplos servidores — usa o que responder primeiro
const servidores = [
buscarDoServidor("servidor-1"),
buscarDoServidor("servidor-2"),
buscarDoServidor("servidor-3"),
];
Promise.any(servidores)
.then(dados => console.log("Dados recebidos:", dados))
.catch(erro => console.error("Todos os servidores falharam."));
Tratamento de erros em Promises
// ✅ Um catch no final trata toda a cadeia
buscarUsuario(1)
.then(usuario => buscarPedidos(usuario.id))
.then(pedidos => processarPedidos(pedidos))
.catch(erro => console.error(erro.message));
// ✅ Catch intermediário — recupera e continua
buscarConfiguracoes()
.catch(erro => {
console.warn("Configurações não encontradas. Usando padrões.");
return { tema: "claro", idioma: "pt-BR" }; // valor de fallback
})
.then(config => {
// config é o valor real OU o fallback
aplicarConfiguracoes(config);
});
// ⚠️ Promessa rejeitada sem catch = UnhandledPromiseRejection
// Sempre adicione .catch() nas suas Promises
buscarUsuario(-1); // ⚠️ sem catch — aviso no console
buscarUsuario(-1).catch(e => console.error(e)); // ✅
Exemplo completo — Dashboard de dados
// Funções que retornam Promises (simulando APIs)
function buscarEstatisticas() {
return new Promise(resolve =>
setTimeout(() => resolve({
usuarios: 1284,
pedidos: 342,
receita: 48750.90,
crescimento: 12.4,
}), 800)
);
}
function buscarUltimosPedidos() {
return new Promise(resolve =>
setTimeout(() => resolve([
{ id: 1001, cliente: "Ana", total: 250, status: "entregue" },
{ id: 1002, cliente: "Bruno", total: 180, status: "enviado" },
{ id: 1003, cliente: "Clara", total: 95, status: "pendente" },
]), 600)
);
}
function buscarAlertas() {
return new Promise(resolve =>
setTimeout(() => resolve([
{ tipo: "aviso", mensagem: "Estoque baixo: Produto #42" },
{ tipo: "info", mensagem: "3 novos usuários hoje" },
]), 400)
);
}
function buscarMetasMes() {
return new Promise(resolve =>
setTimeout(() => resolve({
meta: 50000,
atual: 48750.90,
porcentagem: 97.5,
}), 700)
);
}
// Carrega tudo em paralelo — eficiente!
function carregarDashboard() {
console.log("⏳ Carregando dashboard...");
const inicio = Date.now();
Promise.all([
buscarEstatisticas(),
buscarUltimosPedidos(),
buscarAlertas(),
buscarMetasMes(),
])
.then(([stats, pedidos, alertas, metas]) => {
const tempo = Date.now() - inicio;
console.log(`✅ Dashboard carregado em ${tempo}ms\n`);
// Estatísticas
console.log("📊 ESTATÍSTICAS");
console.log(` Usuários: ${stats.usuarios.toLocaleString("pt-BR")}`);
console.log(` Pedidos: ${stats.pedidos}`);
console.log(` Receita: R$ ${stats.receita.toLocaleString("pt-BR", { minimumFractionDigits: 2 })}`);
console.log(` Crescimento: +${stats.crescimento}%\n`);
// Meta do mês
console.log("🎯 META DO MÊS");
const barraSize = 20;
const preenchido = Math.round((metas.porcentagem / 100) * barraSize);
const barra = "█".repeat(preenchido) + "░".repeat(barraSize - preenchido);
console.log(` [${barra}] ${metas.porcentagem}%`);
console.log(` R$ ${metas.atual.toLocaleString("pt-BR")} de R$ ${metas.meta.toLocaleString("pt-BR")}\n`);
// Últimos pedidos
console.log("🛍️ ÚLTIMOS PEDIDOS");
pedidos.forEach(p => {
const icone = { entregue: "✅", enviado: "📦", pendente: "⏳" }[p.status];
console.log(` ${icone} #${p.id} — ${p.cliente}: R$ ${p.total}`);
});
// Alertas
if (alertas.length > 0) {
console.log("\n🔔 ALERTAS");
alertas.forEach(a => {
const icone = a.tipo === "aviso" ? "⚠️" : "ℹ️";
console.log(` ${icone} ${a.mensagem}`);
});
}
})
.catch(erro => {
console.error(`❌ Erro ao carregar dashboard: ${erro.message}`);
})
.finally(() => {
console.log("\n── Fim do carregamento ──");
});
}
carregarDashboard();
Comparativo: Callbacks vs Promises
// ── Callbacks ──────────────────────────────
buscarUsuario(1, function(err, usuario) {
if (err) return tratarErro(err);
buscarPedidos(usuario.id, function(err, pedidos) {
if (err) return tratarErro(err);
processarPedidos(pedidos, function(err, resultado) {
if (err) return tratarErro(err);
console.log(resultado);
});
});
});
// ── Promises ───────────────────────────────
buscarUsuario(1)
.then(usuario => buscarPedidos(usuario.id))
.then(pedidos => processarPedidos(pedidos))
.then(resultado => console.log(resultado))
.catch(erro => tratarErro(erro));
A diferença é clara. E ainda vai melhorar no próximo artigo, quando chegarmos ao async/await.
Tarefa para você
Implemente um sistema de checkout de e-commerce com Promises:
// Implemente as funções abaixo retornando Promises com setTimeout:
// validarCarrinho(itens)
// → resolve com itens se válido
// → rejeita se carrinho estiver vazio
// verificarEstoque(itens)
// → resolve com itens se todos têm estoque
// → rejeita com qual produto está sem estoque
// calcularFrete(cep)
// → resolve com { valor, prazo }
// → rejeita se CEP inválido (menos de 8 dígitos)
// processarPagamento(total, cartao)
// → resolve com { aprovado: true, codigo }
// → rejeita se cartao.limite < total
// confirmarPedido(dados)
// → resolve com { numeroPedido, previsaoEntrega }
// Monte a cadeia com Promise.all onde possível
// (verificarEstoque e calcularFrete podem ser paralelos)
// Trate cada tipo de erro com mensagem específica
Ver solução — checkout completo com Promises
// ---------------------------------------------------------------
// Catálogo e helpers
// ---------------------------------------------------------------
const ESTOQUE = { caneca: 12, camiseta: 0, adesivo: 300 };
const PRECOS = { caneca: 39.9, camiseta: 79.9, adesivo: 5.0 };
// Todo passo simula latência de rede. O atraso vem antes da decisão,
// então o erro chega assíncrono como chegaria numa API de verdade.
const daquiA = (ms) => (fn) =>
new Promise((resolve, reject) => setTimeout(() => fn(resolve, reject), ms));
const subtotal = (itens) =>
itens.reduce((soma, i) => soma + PRECOS[i.sku] * i.quantidade, 0);
// ---------------------------------------------------------------
// Os cinco passos
// ---------------------------------------------------------------
function validarCarrinho(itens) {
return daquiA(200)((resolve, reject) => {
if (!Array.isArray(itens) || itens.length === 0) {
reject(new Error('CARRINHO_VAZIO: adicione ao menos um item.'));
return;
}
const invalido = itens.find((i) => !PRECOS[i.sku] || i.quantidade < 1);
if (invalido) {
reject(new Error(`ITEM_INVALIDO: ${invalido.sku ?? 'sem sku'}`));
return;
}
resolve(itens);
});
}
function verificarEstoque(itens) {
return daquiA(400)((resolve, reject) => {
const faltando = itens.find((i) => (ESTOQUE[i.sku] ?? 0) < i.quantidade);
if (faltando) {
reject(new Error(`SEM_ESTOQUE: ${faltando.sku}`));
return;
}
resolve(itens);
});
}
function calcularFrete(cep) {
return daquiA(300)((resolve, reject) => {
const limpo = String(cep).replace(/\D/g, '');
if (limpo.length < 8) {
reject(new Error(`CEP_INVALIDO: ${cep}`));
return;
}
// Região pelo primeiro dígito, só para variar o resultado.
const regiao = Number(limpo[0]);
resolve({ valor: 12.9 + regiao * 2.1, prazo: 3 + regiao });
});
}
function processarPagamento(total, cartao) {
return daquiA(700)((resolve, reject) => {
if (cartao.limite < total) {
reject(new Error(
`LIMITE_INSUFICIENTE: precisa de ${total.toFixed(2)}, ` +
`disponível ${cartao.limite.toFixed(2)}`
));
return;
}
resolve({
aprovado: true,
codigo: `AUT-${Math.random().toString(36).slice(2, 10).toUpperCase()}`,
});
});
}
function confirmarPedido(dados) {
return daquiA(250)((resolve) => {
const previsao = new Date();
previsao.setDate(previsao.getDate() + dados.frete.prazo);
resolve({
numeroPedido: `PED-${Date.now().toString().slice(-8)}`,
previsaoEntrega: previsao.toLocaleDateString('pt-BR'),
total: dados.total,
autorizacao: dados.pagamento.codigo,
});
});
}
// ---------------------------------------------------------------
// A cadeia
// ---------------------------------------------------------------
function checkout({ itens, cep, cartao }) {
return validarCarrinho(itens)
.then((itensValidos) =>
// Estoque e frete não dependem um do outro: rodam juntos.
// Em série seriam 400 + 300 ms; assim, os mesmos 400 ms do mais lento.
Promise.all([verificarEstoque(itensValidos), calcularFrete(cep)])
)
.then(([itensOk, frete]) => {
const total = subtotal(itensOk) + frete.valor;
// Devolver o objeto inteiro mantém itens e frete vivos no próximo elo —
// sem isso, confirmarPedido não teria o prazo para calcular a entrega.
return processarPagamento(total, cartao).then((pagamento) => ({
itens: itensOk,
frete,
total,
pagamento,
}));
})
.then(confirmarPedido)
.then((pedido) => {
console.log(`✅ Pedido ${pedido.numeroPedido} confirmado`);
console.log(` Total: R$ ${pedido.total.toFixed(2)}`);
console.log(` Entrega até ${pedido.previsaoEntrega}`);
return pedido;
})
.catch((erro) => {
// Um .catch() no fim pega a rejeição de qualquer elo anterior.
// O prefixo do código dá a mensagem específica de cada falha.
const [codigo] = erro.message.split(':');
const mensagens = {
CARRINHO_VAZIO: 'Seu carrinho está vazio.',
ITEM_INVALIDO: 'Há um item inválido no carrinho.',
SEM_ESTOQUE: 'Um produto ficou sem estoque durante a compra.',
CEP_INVALIDO: 'CEP inválido — confira o endereço de entrega.',
LIMITE_INSUFICIENTE: 'Pagamento recusado por limite insuficiente.',
};
console.error(`❌ ${mensagens[codigo] ?? 'Falha inesperada no checkout.'}`);
console.error(` Detalhe técnico: ${erro.message}`);
// Repropaga: quem chamou decide o que fazer (retentar, avisar a UI…).
throw erro;
});
}
// ---------------------------------------------------------------
// Testando
// ---------------------------------------------------------------
checkout({
itens: [{ sku: 'caneca', quantidade: 2 }, { sku: 'adesivo', quantidade: 5 }],
cep: '01310-100',
cartao: { limite: 500 },
});
// Caminhos de erro:
// checkout({ itens: [], cep: '01310100', cartao: { limite: 500 } });
// → CARRINHO_VAZIO
// checkout({ itens: [{ sku: 'camiseta', quantidade: 1 }], cep: '01310100', cartao: { limite: 500 } });
// → SEM_ESTOQUE
// checkout({ itens: [{ sku: 'caneca', quantidade: 1 }], cep: '123', cartao: { limite: 500 } });
// → CEP_INVALIDO
// checkout({ itens: [{ sku: 'caneca', quantidade: 2 }], cep: '01310100', cartao: { limite: 10 } });
// → LIMITE_INSUFICIENTE
Dois pontos que o exercício cobra: Promise.all só vale onde os passos são independentes — pagamento depende do frete, então fica em série; e o return dentro do .then() é o que encadeia. Sem ele a Promise seguinte não é aguardada e o .catch() final nunca vê o erro.
Uma Promise tem três estados e uma regra que resolve metade das dúvidas: a transição acontece uma vez só e é definitiva — de pendente para resolvida ou rejeitada, nunca de volta. O encadeamento com .then nasce daí, e os quatro combinadores também, cada um respondendo a uma pergunta diferente: all exige que todas deem certo, allSettled quer o destino de cada uma, race aceita a primeira que se assentar e any espera o primeiro sucesso.
Fontes e Referências
- MDN Web Docs — Promise: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise
- MDN Web Docs — Promise.all: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
- MDN Web Docs — Promise.allSettled: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled
- MDN Web Docs — Using Promises: https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Guide/Using_promises
- JavaScript.info — Promises: https://javascript.info/promise-basics
- JavaScript.info — Promise chaining: https://javascript.info/promise-chaining
- JavaScript.info — Promise API: https://javascript.info/promise-api
Exercícios
Exercício 1
No encadeamento do artigo, a terceira etapa chama buscarPedidos(1) com o número cravado. Por que não dá para escrever buscarPedidos(usuario.id) ali?
buscarUsuario(1)
.then(usuario => {
return buscarEndereco(usuario.enderecoId);
})
.then(endereco => {
return buscarPedidos(1); // ← por que não usuario.id?
})
.then(pedidos => console.log(pedidos.length));
Ver resposta
✓ Resposta: Porque usuario não existe nesse escopo. Cada .then() recebe uma função nova, e o único dado que chega até ela é o valor devolvido pelo elo anterior — que aqui é o endereço. O usuário ficou para trás, no escopo do primeiro .then. Essa é a costura que a cadeia de promises não consegue esconder, e ela tem três saídas conhecidas. A primeira é aninhar um nível, colocando a segunda chamada dentro do primeiro .then — funciona, mas reintroduz um pedaço da pirâmide que se queria eliminar. A segunda é carregar tudo adiante, devolvendo um objeto em vez de um valor solto: return buscarEndereco(...).then(endereco => ({ usuario, endereco })). A terceira é Promise.all([usuario, buscarEndereco(...)]), aproveitando que o combinador aceita valores comuns misturados a promises. Nenhuma das três é bonita, e é exatamente por isso que o próximo artigo importa: com async/await todos os valores ficam no mesmo escopo, como em qualquer função síncrona, e o problema simplesmente deixa de existir.
Exercício 2
Três promises: A resolve em 100 ms, B rejeita em 50 ms, C resolve em 300 ms. O que cada combinador devolve, e em que instante?
Promise.all([A, B, C]);
Promise.allSettled([A, B, C]);
Promise.race([A, B, C]);
Promise.any([A, B, C]);
Ver resposta
✓ Resposta: Promise.all rejeita aos 50 ms, com o erro de B — basta uma falhar. allSettled resolve aos 300 ms, esperando todas, com três objetos {status, value} ou {status, reason}. race rejeita aos 50 ms: ela corre para a primeira que se assenta, e assentar inclui falhar. any resolve aos 100 ms com o valor de A: ela ignora rejeições e espera o primeiro sucesso, só rejeitando se todas falharem — e nesse caso com um AggregateError, cujo array .errors traz todas as causas. Duas observações que mudam decisões reais de código: o all rejeitar cedo não cancela A nem C, que seguem rodando até o fim com os resultados descartados; e a diferença entre race e any é justamente o que faz do race a ferramenta de timeout — você quer que a falha vença — e do any a de redundância entre servidores, onde a falha de um não pode derrubar a tentativa.
Exercício 3
Falta uma palavra nesta cadeia. O que ela imprime, e o que acontece se buscarPedidos rejeitar?
buscarUsuario(1)
.then(usuario => { buscarPedidos(usuario.id); })
.then(pedidos => console.log(pedidos.length))
.catch(erro => console.error("falhou:", erro.message));
Ver resposta
✓ Resposta: Falta o return, e isso produz dois defeitos de uma vez. O primeiro: a arrow com chaves e sem return devolve undefined, então a cadeia não espera buscarPedidos — o .then seguinte roda imediatamente com pedidos valendo undefined, e undefined.length lança TypeError. Esse erro é capturado pelo .catch, que imprime falhou: Cannot read properties of undefined (reading 'length') — uma mensagem que não aponta em nada para a causa real. O segundo defeito é mais silencioso: a promise de buscarPedidos foi criada e ficou solta, fora da cadeia. Se ela rejeitar, ninguém a observa, e o resultado é um UnhandledPromiseRejection — no navegador, um aviso no console; no Node, um processo encerrado. O .catch do fim jamais a alcança, porque só trata a cadeia à qual está ligado. É por isso que o estilo idiomático é a arrow sem chaves, .then(usuario => buscarPedidos(usuario.id)): o retorno é implícito e o esquecimento fica impossível.
Exercício 4
A requisição termina em 500 ms e ganha a corrida. O que acontece com o temporizador de 3 segundos? E se fosse o contrário — o timeout vencendo — a requisição para?
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timeout")), 3000)
);
Promise.race([buscarUsuario(1), timeout])
.then(usuario => console.log(usuario.nome))
.catch(erro => console.error(erro.message));
Ver resposta
✓ Resposta: O temporizador continua vivo e dispara aos 3 segundos, mesmo com a corrida já decidida. Rejeitar uma promise que ninguém mais observa não causa erro visível — a race já se assentou e ignora o segundo resultado —, mas o setTimeout segue agendado, segurando na memória o callback e tudo o que ele fecha por closure; no Node, ele ainda impede o processo de terminar até disparar. Em código que faz isso a cada requisição, são milhares de temporizadores pendurados. A correção é guardar o id e limpá-lo num .finally() da corrida. Quanto à segunda pergunta: não, a requisição não para. A race escolhe de quem você vai ouvir a resposta, não quem continua trabalhando — a chamada segue até o fim, ocupando conexão, e um efeito colateral no servidor acontece de qualquer jeito. Cancelar de verdade exige que a própria operação saiba ser cancelada, que é o papel do AbortController passado ao fetch pela opção signal.
Exercício 5
buscarUsuario(-1) sempre rejeita. O que cada uma das três cadeias imprime?
// A
buscarUsuario(-1)
.catch(e => { console.warn("usando padrão"); return { nome: "convidado" }; })
.then(u => console.log(u.nome));
// B
buscarUsuario(-1)
.then(u => console.log(u.nome))
.catch(e => console.warn("erro"));
// C
buscarUsuario(-1)
.catch(e => console.warn("erro"))
.then(u => console.log(u.nome));
Ver resposta
✓ Resposta: A imprime usando padrão e depois convidado. B imprime só erro. C imprime erro e em seguida lança TypeError: Cannot read properties of undefined. A chave para as três é que o .catch não encerra a cadeia: ele a recoloca no trilho do sucesso, e o que ele devolve vira o valor do próximo .then. Em A isso é deliberado e é o padrão de fallback — captura a falha, devolve um valor razoável, e a cadeia segue como se nada tivesse acontecido. Em B o .catch está no fim, então o .then anterior é simplesmente pulado quando há rejeição. Em C está o acidente: o console.warn não devolve nada, o .catch resolve com undefined, e o .then seguinte roda assim mesmo. A regra prática que resolve os três casos é decidir a intenção antes de escolher a posição — .catch no fim para relatar, .catch no meio só quando ele devolve um valor de substituição e a cadeia realmente pode continuar.