Escrevendo Cadeias Async Mais Limpas com Promise.try
Se você já escreveu uma cadeia de promises que começa com uma função que pode ser síncrona ou pode ser assíncrona, provavelmente já se deparou com um problema inconveniente: onde você coloca seu .catch()?
Erros síncronos lançados antes que uma promise seja retornada não serão capturados por .catch() a menos que você já esteja dentro de um contexto de promise. Promise.try() resolve isso de forma elegante, fornecendo um único ponto de entrada consistente para qualquer cadeia de promises — independentemente de a função que você está chamando ser síncrona, assíncrona ou algo intermediário.
Principais Conclusões
- Throws síncronos dentro de funções que às vezes retornam promises podem escapar completamente do
.catch(), levando a exceções não tratadas. Promise.try()executa uma função imediatamente e envolve tanto retornos síncronos quanto throws síncronos em uma promise adequada, fornecendo um caminho unificado de tratamento de erros.- Diferentemente de
Promise.resolve(fn()), ele captura erros síncronos. Diferentemente dePromise.resolve().then(fn), ele executa imediatamente sem adiar para uma microtask. - É mais adequado para cadeias baseadas em
.then()com pontos de entrada síncronos/assíncronos mistos — não como substituto paraasync/await.
O Problema: Erros Síncronos Que Escapam da Sua Cadeia .catch()
Considere um carregador de dados que lê de um cache de forma síncrona ou busca de uma API de forma assíncrona dependendo das condições:
function loadData(key) {
const cached = getFromCache(key) // pode lançar exceção de forma síncrona
if (cached) return cached
return fetch(`/api/data/${key}`).then(res => res.json())
}
loadData('user-1')
.then(data => render(data))
.catch(err => handleError(err)) // ⚠️ Não capturará throws síncronos de loadData
Se getFromCache lançar uma exceção de forma síncrona, esse erro nunca é capturado por .catch(). O throw acontece antes que qualquer promise exista, então ele escapa completamente da cadeia e se torna uma exceção não tratada.
Há uma segunda sutileza aqui que vale a pena notar: quando loadData retorna um valor em cache diretamente (um não-thenable), chamar .then() nele também falhará porque valores simples não têm um método .then(). Esta função é inerentemente frágil — ela retorna uma promise em um ramo e um valor bruto em outro. Promise.try() resolve ambos os problemas sempre produzindo uma promise.
Como Promise.try() Corrige Isso
Promise.try(fn) executa a função fornecida imediatamente e envolve o resultado em uma promise. Se a função retornar um valor simples, ela resolve com esse valor. Se retornar uma promise, ela adota essa promise. Se lançar uma exceção de forma síncrona, ela converte esse erro em uma rejeição.
Isso fornece um único ponto de entrada para lidar com erros síncronos e assíncronos através do mesmo .catch():
Promise.try(() => loadData('user-1'))
.then(data => render(data))
.catch(err => handleError(err)) // ✅ Captura tanto throws síncronos quanto rejeições assíncronas
Sem casos especiais. Sem envolver em try/catch antes de iniciar a cadeia. Tudo flui através de .catch() como esperado.
Discover how at OpenReplay.com.
Como Difere de Promise.resolve().then(fn) e Promise.resolve(fn())
Esses dois padrões são comumente usados como soluções alternativas, mas se comportam de maneiras diferentes em aspectos importantes.
Promise.resolve(fn()) chama fn() imediatamente, fora de um contexto de promise. Um throw síncrono aqui é uma exceção não capturada, não uma rejeição.
Promise.resolve().then(fn) adia a execução de fn para uma microtask. Isso significa que fn não é executada imediatamente — o que pode causar problemas sutis de temporização e torna o comportamento menos previsível quando você precisa de execução imediata.
Promise.try(fn) executa fn imediatamente e captura qualquer throw síncrono como uma rejeição. É o mais previsível dos três para iniciar cadeias de promises.
| Padrão | Executa fn imediatamente | Captura throws síncronos |
|---|---|---|
Promise.resolve(fn()) | ✅ | ❌ |
Promise.resolve().then(fn) | ❌ | ✅ |
Promise.try(fn) | ✅ | ✅ |
Casos de Uso Práticos no Frontend
Promise.try() se encaixa naturalmente em padrões async de JavaScript onde o comportamento depende de condições em tempo de execução:
Funções utilitárias que podem retornar dados em cache de forma síncrona ou buscá-los de forma assíncrona:
Promise.try(() => getUserFromCacheOrAPI(userId))
.then(updateUI)
.catch(showErrorBanner)
Fluxos de trabalho assíncronos condicionais onde uma etapa de validação pode lançar exceção antes que qualquer trabalho assíncrono comece:
Promise.try(() => {
validateInput(formData) // lança exceção se inválido
return submitForm(formData) // retorna uma promise
})
.then(handleSuccess)
.catch(handleError)
Suporte de Navegadores e Compatibilidade
Promise.try() foi incluído no ECMAScript 2025 (ES2025) e é suportado no Chrome 128+, Firefox 134+, Safari 18.2+ e Node.js 22.7.0+. Você pode verificar o suporte atual dos navegadores no Can I Use, que rastreia o status de implementação nos principais navegadores e runtimes.
Para ambientes mais antigos, você pode fazer um polyfill com um wrapper simples:
Promise.try = Promise.try || function(fn) {
return new Promise(resolve => resolve(fn()))
}
Isso funciona porque o executor do new Promise é executado de forma síncrona, então fn() é chamada imediatamente. Se fn() lançar uma exceção, o construtor Promise a captura e a transforma em uma rejeição. Se fn() retornar um thenable, resolve o adota.
Conclusão
Promise.try() não é um substituto para async/await. É uma ferramenta pequena e focada para uma situação específica: iniciar uma cadeia de promises quando o ponto de entrada pode lançar exceção de forma síncrona ou retornar uma mistura de valores e promises.
Se você já está dentro de uma função async, um bloco try/catch lida com ambos os casos naturalmente. Mas quando você está trabalhando com cadeias .then() — especialmente em torno de funções utilitárias ou carregadores de dados com lógica condicional — Promise.try() mantém seu tratamento de erros consistente e suas cadeias limpas.
Perguntas Frequentes
Sim. Se a função que você passa para Promise.try() for async, ela retorna uma promise, e Promise.try() adota essa promise. Funciona da mesma forma que passar qualquer função que retorna promise. O principal benefício de Promise.try() é para funções que podem não retornar uma promise, ou que podem lançar exceção antes de retornar uma.
Não. Dentro de uma função async, um bloco try/catch padrão já captura tanto throws síncronos quanto rejeições aguardadas. Promise.try() é projetado para cadeias estilo .then() onde você precisa de um ponto de entrada seguro. Se você já está usando async/await, provavelmente não precisa de Promise.try().
O polyfill comum usando new Promise(resolve => resolve(fn())) é funcionalmente equivalente à implementação nativa. Ele executa fn imediatamente, captura throws síncronos via construtor Promise e adota thenables através de resolve. É seguro para uso em produção em ambientes que não têm suporte nativo.
Promise.resolve(fn()) chama fn fora de um contexto de promise, então throws síncronos se tornam exceções não capturadas. Promise.resolve().then(fn) captura throws mas adia a execução para uma microtask, o que significa que fn não é executada imediatamente. Promise.try(fn) é o único padrão que tanto executa fn imediatamente quanto captura erros síncronos como rejeições.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.