Async/Await et reject : pourquoi c'est un casse-tête pour les débutants ?
\nAvez-vous déjà écrit une fonction async, attendu une réponse du serveur, puis attrapé une UnhandledPromiseRejection ? Cela vous dit quelque chose ? C'est l'un des problèmes les plus courants rencontrés par les développeurs qui passent à async/await en TypeScript.
Sur Stack Overflow, la question "How to reject in async/await syntax?" a accumulé des milliers de vues. Et ce n'est pas pour rien. À première vue, il semble suffisant de retourner Promise.reject() — et le tour est joué. Mais dans les projets réels, c'est plus complexe : les types, les chaînes d'appels, la perte de contexte des erreurs.
Dans ce guide, nous allons voir comment rejeter correctement les promesses dans les fonctions async TypeScript, apprendre à distinguer throw de return Promise.reject() et examiner des exemples pratiques issus de projets réels.
Les bases : comment fonctionne le reject dans async/await
\nAvant de plonger dans les détails, rappelons les fondamentaux. Toute fonction async en TypeScript retourne une Promise. Si un throw se produit à l'intérieur ou si Promise.reject() est retourné, la promesse passe à l'état rejected.
// Exemple simple de reject dans async/await\nasync function fetchUser(id: number): Promise<User> {\n if (id <= 0) {\n throw new Error('Invalid user ID');\n }\n // ...logique de récupération de l'utilisateur\n}\n\nIci, throw se transforme automatiquement en reject. Mais que faire si nous voulons retourner une promesse déjà rejetée ?
throw vs return Promise.reject()
\nEn TypeScript, les deux approches fonctionnent, mais il y a des nuances :
\n\n- \n
- throw — une manière plus idiomatique. Il arrête l'exécution de la fonction et transmet le contrôle au bloc
catchle plus proche. \n - return Promise.reject() — retourne explicitement une promesse rejetée. Utile lorsque vous devez transmettre une erreur depuis une autre fonction. \n
// Différence entre throw et return Promise.reject()\nasync function processOrder(orderId: string): Promise<OrderStatus> {\n // Option 1 : throw\n if (!orderId) {\n throw new Error('Order ID is required');\n }\n \n // Option 2 : return Promise.reject\n if (orderId.startsWith('test_')) {\n return Promise.reject(new Error('Test orders are not allowed'));\n }\n \n return { status: 'processing' };\n}\n\nImportant : TypeScript ne déduit pas toujours correctement les types avec return Promise.reject(). Si la fonction attend un Promise<T> et que vous retournez Promise.reject(), une erreur de typage peut survenir. Dans ces cas, il est préférable d'utiliser throw.
Erreurs typiques lors du reject dans les fonctions async
\nMême les développeurs expérimentés en TypeScript commettent parfois des erreurs classiques. Voici le top 3 :
\n\n1. Oubli de gérer le reject dans le code appelant
\nasync function main() {\n // Mauvais : pas de try/catch\n const user = await fetchUser(-1);\n console.log(user); // UnhandledPromiseRejection !\n}\n\n// Bon :\nasync function mainSafe() {\n try {\n const user = await fetchUser(-1);\n console.log(user);\n } catch (error) {\n console.error('Échec de la récupération de l\'utilisateur :', error);\n }\n}\n\n2. Typage incorrect des erreurs
\nEn TypeScript, le bloc catch attrape par défaut unknown. C'est correct, mais parfois les développeurs tentent d'accéder à error.message sans vérification.
try {\n await riskyOperation();\n} catch (error) {\n // Erreur : error est de type unknown\n console.log(error.message); // ❌ Property 'message' does not exist on type 'unknown'\n \n // Correct :\n if (error instanceof Error) {\n console.log(error.message);\n } else {\n console.log