Kategorie: runtimeSchwierigkeit: FortgeschrittenVeröffentlicht: 2024-12-18
Häufige Async/Await-Fallstricke und Lösungen
Async/await ist eine leistungsstarke Funktion in JavaScript zur Behandlung asynchroner Operationen, bringt aber eigene häufige Fallstricke und Herausforderungen mit sich. Das Verständnis dieser Probleme ist entscheidend für das Schreiben zuverlässigen asynchronen Codes.
Häufige Fallstricke
1. Vergessenes Await bei Promises
// Problem: Funktion kehrt zurück, bevor Promise aufgelöst wird async function holeBenutzerdaten(benutzerId) { const benutzerdaten = fetch(`/api/benutzer/${benutzerId}`); // Fehlendes await console.log(benutzerdaten); // Gibt Promise-Objekt aus, nicht die Daten return benutzerdaten; } // Lösung: Korrekte Verwendung von await async function holeBenutzerdatenFixed(benutzerId) { const antwort = await fetch(`/api/benutzer/${benutzerId}`); const benutzerdaten = await antwort.json(); console.log(benutzerdaten); // Gibt tatsächliche Daten aus return benutzerdaten; }
2. Lücken in der Fehlerbehandlung
// Problem: Fehler in asynchronen Operationen werden nicht abgefangen async function verarbeiteDaten(daten) { const ergebnis = await asyncOperation(daten); return ergebnis; // Fehler werden unbehandelt weitergegeben } // Lösung: Korrekte Fehlerbehandlung async function verarbeiteDatenFixed(daten) { try { const ergebnis = await asyncOperation(daten); return ergebnis; } catch (fehler) { console.error('Fehler bei der Datenverarbeitung:', fehler); // Erwägen Sie, den Fehler weiterzuwerfen oder einen Standardwert zurückzugeben throw new Error(`Datenverarbeitung fehlgeschlagen: ${fehler.message}`); } }
3. Promise.all Fehlerbehandlung
// Problem: Ein Fehler stoppt alle Operationen async function holeAlleBenutzer(benutzerIds) { const benutzer = await Promise.all( benutzerIds.map(id => fetch(`/api/benutzer/${id}`)) ); return benutzer; } // Lösung: Einzelne Fehler behandeln async function holeAlleBenutzerFixed(benutzerIds) { const benutzerPromises = benutzerIds.map(async id => { try { const antwort = await fetch(`/api/benutzer/${id}`); return await antwort.json(); } catch (fehler) { console.error(`Fehler beim Abrufen von Benutzer ${id}:`, fehler); return null; // Oder passender Standardwert } }); const benutzer = await Promise.all(benutzerPromises); return benutzer.filter(benutzer => benutzer !== null); }
Fehlerbehandlungsmuster
1. Wiederholungsmuster
async function fetchMitWiederholung(url, optionen = {}, maxVersuche = 3) { let letzterFehler; for (let versuch = 0; versuch < maxVersuche; versuch++) { try { const antwort = await fetch(url, optionen); if (!antwort.ok) { throw new Error(`HTTP-Fehler! Status: ${antwort.status}`); } return await antwort.json(); } catch (fehler) { console.warn(`Versuch ${versuch + 1} fehlgeschlagen:`, fehler); letzterFehler = fehler; if (versuch < maxVersuche - 1) { const verzoegerung = Math.pow(2, versuch) * 1000; // Exponentieller Rückzug await new Promise(resolve => setTimeout(resolve, verzoegerung)); } } } throw new Error(`Nach ${maxVersuche} Versuchen fehlgeschlagen: ${letzterFehler.message}`); } // Verwendung try { const daten = await fetchMitWiederholung('/api/daten'); console.log('Erfolg:', daten); } catch (fehler) { console.error('Alle Versuche fehlgeschlagen:', fehler); }
2. Timeout-Muster
function timeout(promise, ms) { const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { reject(new Error(`Operation nach ${ms}ms abgelaufen`)); }, ms); }); return Promise.race([promise, timeoutPromise]); } // Verwendung async function fetchMitTimeout(url, ms = 5000) { try { const antwort = await timeout(fetch(url), ms); return await antwort.json(); } catch (fehler) { if (fehler.message.includes('abgelaufen')) { console.error('Anfrage abgelaufen'); } else { console.error('Anfrage fehlgeschlagen:', fehler); } throw fehler; } }
3. Schutzschalter-Muster
class Schutzschalter { constructor(fn, optionen = {}) { this.fn = fn; this.fehler = 0; this.maxFehler = optionen.maxFehler || 3; this.resetTimeout = optionen.resetTimeout || 60000; this.zustand = 'GESCHLOSSEN'; // GESCHLOSSEN, OFFEN, HALB_OFFEN } async ausfuehren(...args) { if (this.zustand === 'OFFEN') { throw new Error('Schutzschalter ist OFFEN'); } try { const ergebnis = await this.fn(...args); this.zuruecksetzen(); return ergebnis; } catch (fehler) { this.fehler++; if (this.fehler >= this.maxFehler) { this.schalterAusloesen(); } throw fehler; } } schalterAusloesen() { this.zustand = 'OFFEN'; setTimeout(() => { this.zustand = 'HALB_OFFEN'; }, this.resetTimeout); } zuruecksetzen() { this.fehler = 0; this.zustand = 'GESCHLOSSEN'; } } // Verwendung const schalter = new Schutzschalter( async (url) => { const antwort = await fetch(url); return antwort.json(); }, { maxFehler: 3, resetTimeout: 10000 } ); async function fetchMitSchutzschalter(url) { try { return await schalter.ausfuehren(url); } catch (fehler) { if (fehler.message === 'Schutzschalter ist OFFEN') { return { fehler: 'Dienst vorübergehend nicht verfügbar' }; } throw fehler; } }
Asynchronen Code testen
describe('Asynchrone Operationen', () => { it('sollte erfolgreiche Anfragen behandeln', async () => { const daten = await fetchMitWiederholung('/api/test'); expect(daten).toBeDefined(); }); it('sollte fehlgeschlagene Anfragen wiederholen', async () => { // Mock für fehlschlagende Anfrage, die beim dritten Versuch erfolgreich ist let versuche = 0; jest.spyOn(global, 'fetch').mockImplementation(() => { versuche++; if (versuche < 3) { return Promise.reject(new Error('Netzwerkfehler')); } return Promise.resolve(new Response('{"erfolg": true}')); }); const daten = await fetchMitWiederholung('/api/test'); expect(daten.erfolg).toBe(true); expect(versuche).toBe(3); }); it('sollte Timeouts behandeln', async () => { await expect( fetchMitTimeout('/api/test', 100) ).rejects.toThrow('Operation abgelaufen'); }); });
Best Practices
-
Promises immer korrekt verketten
// Schlecht promise.then(ergebnis => { return inDatenbankSpeichern(ergebnis) }).then(gespeichert => { return emailSenden(gespeichert) }); // Gut async function verarbeiten() { const ergebnis = await promise; const gespeichert = await inDatenbankSpeichern(ergebnis); return await emailSenden(gespeichert); }
-
Alle Fehlerfälle behandeln
async function robusteFunktion() { try { const ergebnis = await riskanteFunktion(); return ergebnis; } catch (fehler) { if (fehler instanceof NetzwerkFehler) { // Netzwerkfehler behandeln return await ausweichFunktion(); } else if (fehler instanceof ValidierungsFehler) { // Validierungsfehler behandeln return standardWert; } else { // Unbekannte Fehler behandeln console.error('Unbekannter Fehler:', fehler); throw fehler; } } }
-
Promise-Methoden angemessen verwenden
// Für parallele Operationen, die alle erfolgreich sein müssen const ergebnisse = await Promise.all([op1(), op2(), op3()]); // Für parallele Operationen, bei denen einige fehlschlagen können const ergebnisse = await Promise.allSettled([op1(), op2(), op3()]); // Für Operationen, bei denen nur der erste Erfolg zählt const ergebnis = await Promise.any([op1(), op2(), op3()]); // Für Operationen, bei denen die erste Fertigstellung zählt const ergebnis = await Promise.race([op1(), op2(), op3()]);
Performance-Überlegungen
-
Parallele vs. Sequentielle Ausführung
// Sequentiell (langsamer) const ergebnis1 = await operation1(); const ergebnis2 = await operation2(); // Parallel (schneller) const [ergebnis1, ergebnis2] = await Promise.all([ operation1(), operation2() ]);
-
Promise-Caching
class PromiseCache { constructor() { this.cache = new Map(); } async get(schluessel, produzent) { if (!this.cache.has(schluessel)) { this.cache.set(schluessel, produzent()); } return this.cache.get(schluessel); } invalidieren(schluessel) { this.cache.delete(schluessel); } }
-
Ressourcen-Bereinigung
async function mitBereinigung(ressource, operation) { try { return await operation(ressource); } finally { await ressource.bereinigen(); } }