Écrire des chaînes asynchrones plus propres avec Promise.try
Si vous avez déjà écrit une chaîne de promesses qui commence par une fonction qui peut être synchrone ou peut être asynchrone, vous avez probablement rencontré un problème gênant : où placer votre .catch() ?
Les erreurs synchrones levées avant qu’une promesse ne soit retournée ne seront pas capturées par .catch() à moins que vous ne soyez déjà dans un contexte de promesse. Promise.try() résout ce problème élégamment en vous fournissant un point d’entrée unique et cohérent pour toute chaîne de promesses — que la fonction que vous appelez soit synchrone, asynchrone, ou entre les deux.
Points clés à retenir
- Les exceptions synchrones levées dans des fonctions qui retournent parfois des promesses peuvent échapper entièrement à
.catch(), entraînant des exceptions non gérées. Promise.try()exécute une fonction immédiatement et encapsule à la fois les retours synchrones et les exceptions synchrones dans une promesse appropriée, vous offrant un chemin unifié de gestion des erreurs.- Contrairement à
Promise.resolve(fn()), elle capture les erreurs synchrones. Contrairement àPromise.resolve().then(fn), elle s’exécute immédiatement sans reporter l’exécution à une microtâche. - Elle est particulièrement adaptée aux chaînes basées sur
.then()avec des points d’entrée mixtes sync/async — et non comme remplacement d’async/await.
Le problème : les erreurs synchrones qui échappent à votre chaîne .catch()
Considérez un chargeur de données qui lit depuis un cache de manière synchrone ou récupère des données depuis une API de manière asynchrone selon les conditions :
function loadData(key) {
const cached = getFromCache(key) // may throw synchronously
if (cached) return cached
return fetch(`/api/data/${key}`).then(res => res.json())
}
loadData('user-1')
.then(data => render(data))
.catch(err => handleError(err)) // ⚠️ Won't catch sync throws from loadData
Si getFromCache lève une exception de manière synchrone, cette erreur n’est jamais capturée par .catch(). L’exception se produit avant qu’une promesse n’existe, elle échappe donc entièrement à la chaîne et devient une exception non gérée.
Il y a une deuxième subtilité ici qui mérite d’être notée : lorsque loadData retourne directement une valeur mise en cache (une valeur non-thenable), l’appel de .then() sur celle-ci échouera également car les valeurs simples n’ont pas de méthode .then(). Cette fonction est intrinsèquement fragile — elle retourne une promesse dans une branche et une valeur brute dans l’autre. Promise.try() résout ces deux problèmes en produisant toujours une promesse.
Comment Promise.try() corrige cela
Promise.try(fn) exécute la fonction fournie immédiatement et encapsule le résultat dans une promesse. Si la fonction retourne une valeur simple, elle se résout avec cette valeur. Si elle retourne une promesse, elle adopte cette promesse. Si elle lève une exception de manière synchrone, elle convertit cette erreur en rejet.
Cela vous donne un point d’entrée unique pour gérer à la fois les erreurs synchrones et asynchrones via le même .catch() :
Promise.try(() => loadData('user-1'))
.then(data => render(data))
.catch(err => handleError(err)) // ✅ Catches both sync throws and async rejections
Pas de cas particulier. Pas d’encapsulation dans try/catch avant de démarrer la chaîne. Tout passe par .catch() comme prévu.
Discover how at OpenReplay.com.
En quoi elle diffère de Promise.resolve().then(fn) et Promise.resolve(fn())
Ces deux modèles sont couramment utilisés comme solutions de contournement, mais ils se comportent différemment de manière importante.
Promise.resolve(fn()) appelle fn() immédiatement, en dehors d’un contexte de promesse. Une exception synchrone ici est une exception non capturée, pas un rejet.
Promise.resolve().then(fn) reporte l’exécution de fn à une microtâche. Cela signifie que fn ne s’exécute pas immédiatement — ce qui peut causer des problèmes de timing subtils et rend le comportement moins prévisible lorsque vous avez besoin d’une exécution immédiate.
Promise.try(fn) exécute fn immédiatement et capture toute exception synchrone comme un rejet. C’est le plus prévisible des trois pour démarrer des chaînes de promesses.
| Modèle | Exécute fn immédiatement | Capture les exceptions sync |
|---|---|---|
Promise.resolve(fn()) | ✅ | ❌ |
Promise.resolve().then(fn) | ❌ | ✅ |
Promise.try(fn) | ✅ | ✅ |
Cas d’usage pratiques en frontend
Promise.try() s’intègre naturellement dans les modèles asynchrones JavaScript où le comportement dépend des conditions d’exécution :
Fonctions utilitaires qui peuvent retourner des données mises en cache de manière synchrone ou les récupérer de manière asynchrone :
Promise.try(() => getUserFromCacheOrAPI(userId))
.then(updateUI)
.catch(showErrorBanner)
Workflows asynchrones conditionnels où une étape de validation peut lever une exception avant que tout travail asynchrone ne commence :
Promise.try(() => {
validateInput(formData) // throws if invalid
return submitForm(formData) // returns a promise
})
.then(handleSuccess)
.catch(handleError)
Support navigateur et compatibilité
Promise.try() a été incluse dans ECMAScript 2025 (ES2025) et est supportée dans Chrome 128+, Firefox 134+, Safari 18.2+, et Node.js 22.7.0+. Vous pouvez vérifier le support actuel des navigateurs sur Can I Use, qui suit le statut d’implémentation dans les principaux navigateurs et environnements d’exécution.
Pour les environnements plus anciens, vous pouvez la polyfiller avec un wrapper simple :
Promise.try = Promise.try || function(fn) {
return new Promise(resolve => resolve(fn()))
}
Cela fonctionne car l’exécuteur du new Promise s’exécute de manière synchrone, donc fn() est appelée immédiatement. Si fn() lève une exception, le constructeur Promise la capture et la transforme en rejet. Si fn() retourne un thenable, resolve l’adopte.
Conclusion
Promise.try() n’est pas un remplacement pour async/await. C’est un outil petit et ciblé pour une situation spécifique : démarrer une chaîne de promesses lorsque le point d’entrée peut lever une exception de manière synchrone ou retourner un mélange de valeurs et de promesses.
Si vous êtes déjà dans une fonction async, un bloc try/catch gère naturellement les deux cas. Mais lorsque vous travaillez avec des chaînes .then() — en particulier autour de fonctions utilitaires ou de chargeurs de données avec une logique conditionnelle — Promise.try() maintient votre gestion des erreurs cohérente et vos chaînes propres.
FAQ
Oui. Si la fonction que vous passez à Promise.try() est async, elle retourne une promesse, et Promise.try() adopte cette promesse. Cela fonctionne de la même manière que passer n'importe quelle fonction retournant une promesse. Le principal avantage de Promise.try() est pour les fonctions qui peuvent ne pas retourner de promesse du tout, ou qui peuvent lever une exception avant d'en retourner une.
Non. À l'intérieur d'une fonction async, un bloc try/catch standard capture déjà à la fois les exceptions synchrones et les rejets awaités. Promise.try() est conçue pour les chaînes de style .then() où vous avez besoin d'un point d'entrée sûr. Si vous utilisez déjà async/await, vous n'avez probablement pas besoin de Promise.try().
Le polyfill courant utilisant new Promise(resolve => resolve(fn())) est fonctionnellement équivalent à l'implémentation native. Il exécute fn immédiatement, capture les exceptions synchrones via le constructeur Promise, et adopte les thenables via resolve. Il est sûr pour une utilisation en production dans les environnements qui ne disposent pas du support natif.
Promise.resolve(fn()) appelle fn en dehors d'un contexte de promesse, donc les exceptions synchrones deviennent des exceptions non capturées. Promise.resolve().then(fn) capture les exceptions mais reporte l'exécution à une microtâche, ce qui signifie que fn ne s'exécute pas immédiatement. Promise.try(fn) est le seul modèle qui à la fois exécute fn immédiatement et capture les erreurs synchrones comme des rejets.
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.