Async/Await und reject: Warum ist das für Anfänger eine Herausforderung?
\nHaben Sie jemals eine async-Funktion geschrieben, auf eine Antwort vom Server gewartet und dann einen UnhandledPromiseRejection erhalten? Kommt Ihnen bekannt vor? Dies ist eines der häufigsten Probleme, mit denen Entwickler konfrontiert werden, wenn sie auf async/await in TypeScript umsteigen.
Auf Stack Overflow hat die Frage "How to reject in async/await syntax?" tausende Aufrufe erhalten. Und das zu Recht. Auf den ersten Blick scheint es, als ob es ausreicht, einfach Promise.reject() zurückzugeben – und schon ist die Sache erledigt. Aber in realen Projekten ist es komplizierter: Typen, Aufrufketten, Verlust des Fehlerkontexts.
In diesem Leitfaden werden wir analysieren, wie man Promises richtig ablehnt in async-Funktionen mit TypeScript, lernen, throw von return Promise.reject() zu unterscheiden, und uns praktische Beispiele aus echten Projekten ansehen.
Grundlagen: Wie funktioniert reject in async/await
\nBevor wir in die Tiefe gehen, erinnern wir uns an die Grundlagen. Jede async-Funktion in TypeScript gibt ein Promise zurück. Wenn innerhalb der Funktion ein throw auftritt oder Promise.reject() zurückgegeben wird, wechselt das Promise in den Zustand rejected.
// Einfachstes Beispiel für reject in async/await\nasync function fetchUser(id: number): Promise<User> {\n if (id <= 0) {\n throw new Error('Ungültige Benutzer-ID');\n }\n // ...Logik zum Abrufen des Benutzers\n}\n\nHier wird throw automatisch zu einem reject. Aber was, wenn wir ein bereits abgelehntes Promise zurückgeben möchten?
throw vs return Promise.reject()
\nIn TypeScript funktionieren beide Ansätze, aber es gibt Nuancen:
\n\n- \n
- throw – die idiomatischere Methode. Sie stoppt die Ausführung der Funktion und übergibt die Kontrolle an den nächsten
catch-Block. \n - return Promise.reject() – gibt explizit ein abgelehntes Promise zurück. Nützlich, wenn ein Fehler aus einer anderen Funktion übergeben werden soll. \n
// Unterschied zwischen throw und return Promise.reject()\nasync function processOrder(orderId: string): Promise<OrderStatus> {\n // Variante 1: throw\n if (!orderId) {\n throw new Error('Bestell-ID ist erforderlich');\n }\n \n // Variante 2: return Promise.reject\n if (orderId.startsWith('test_')) {\n return Promise.reject(new Error('Testbestellungen sind nicht erlaubt'));\n }\n \n return { status: 'wird bearbeitet' };\n}\n\nWichtig: TypeScript leitet die Typen bei return Promise.reject() nicht immer korrekt ab. Wenn die Funktion Promise<T> erwartet, Sie aber Promise.reject() zurückgeben, kann ein Typisierungsfehler auftreten. In solchen Fällen ist es besser, throw zu verwenden.
Typische Fehler bei reject in async-Funktionen
\nSogar erfahrene TypeScript-Entwickler machen manchmal klassische Fehler. Hier die Top 3:
\n\n1. Vergessen, reject im aufrufenden Code zu behandeln
\nasync function main() {\n // Schlecht: kein try/catch\n const user = await fetchUser(-1);\n console.log(user); // UnhandledPromiseRejection!\n}\n\n// Gut:\nasync function mainSafe() {\n try {\n const user = await fetchUser(-1);\n console.log(user);\n } catch (error) {\n console.error('Fehler beim Abrufen des Benutzers:', error);\n }\n}\n\n2. Falsche Typisierung von Fehlern
\nIn TypeScript fängt der catch-Block standardmäßig unknown. Das ist korrekt, aber manchmal versuchen Entwickler, auf error.message zuzugreifen, ohne eine Prüfung durchzuführen.
try {\n await riskyOperation();\n} catch (error) {\n // Fehler: error ist unknown\n console.log(error.message); // ❌ Property 'message' does not exist on type 'unknown'\n \n // Richtig:\n if (error instanceof Error) {\n console.log(error.message);\n } else {\n console.log