JavaScript Fortgeschritten

Promises in JavaScript verstehen

Promises in JavaScript verstehen

Promises sind JavaScripts Art, mit asynchronem Code umzugehen. Ein Promise repräsentiert einen Wert, der jetzt, später oder nie verfügbar sein könnte.

Was ist ein Promise?

Ein Promise ist ein Versprechen auf ein zukünftiges Ergebnis:

// Stell dir vor: Du bestellst Pizza
const pizzaVersprechen = bestellePizza();

// Du weißt nicht WANN sie kommt
// Aber du hast ein Versprechen, dass:
// - Sie entweder geliefert wird (fulfilled)
// - Oder ein Problem auftritt (rejected)

Die drei Zustände

┌─────────────┐
│   pending   │  ← Warte auf Ergebnis
└─────────────┘

       ├──────────────────┐
       │                  │
       V                  V
┌─────────────┐    ┌─────────────┐
│  fulfilled  │    │  rejected   │
│  (success)  │    │   (error)   │
└─────────────┘    └─────────────┘

Promise erstellen

Mit dem Promise-Konstruktor

const meinPromise = new Promise((resolve, reject) => {
    // Asynchrone Operation...

    // Bei Erfolg:
    resolve("Ergebnis");

    // Bei Fehler:
    reject(new Error("Etwas ging schief"));
});

Praktisches Beispiel

function warteZufaellig() {
    return new Promise((resolve, reject) => {
        const wartezeit = Math.random() * 3000;

        setTimeout(() => {
            if (wartezeit > 2000) {
                reject(new Error("Zu lange gewartet!"));
            } else {
                resolve(`Fertig nach ${Math.round(wartezeit)}ms`);
            }
        }, wartezeit);
    });
}

// Verwenden
warteZufaellig()
    .then(ergebnis => console.log(ergebnis))
    .catch(fehler => console.error(fehler.message));

Sofort aufgelöste Promises

// Sofort fulfilled
const sofortFertig = Promise.resolve("Direkt verfügbar");

// Sofort rejected
const sofortFehler = Promise.reject(new Error("Sofort fehlgeschlagen"));

// Nützlich für einheitliche APIs
function holeDaten(id) {
    if (cache[id]) {
        return Promise.resolve(cache[id]);  // Aus Cache
    }
    return fetch(`/api/data/${id}`);  // Vom Server
}

then, catch, finally

then - Bei Erfolg

const promise = fetch("/api/users");

promise.then(response => {
    console.log("Erfolg!", response);
});

// Kurzform
fetch("/api/users").then(response => console.log(response));

catch - Bei Fehler

fetch("/api/users")
    .then(response => response.json())
    .catch(error => {
        console.error("Fehler:", error.message);
    });

finally - Immer ausführen

let isLoading = true;

fetch("/api/users")
    .then(response => response.json())
    .then(users => {
        displayUsers(users);
    })
    .catch(error => {
        showError(error);
    })
    .finally(() => {
        isLoading = false;  // Immer ausführen, egal ob Erfolg oder Fehler
        hideSpinner();
    });

Promise-Ketten

then gibt immer ein neues Promise zurück - perfekt zum Verketten:

fetch("/api/users/1")
    .then(response => {
        console.log("1. Response erhalten");
        return response.json();  // Gibt Promise zurück
    })
    .then(user => {
        console.log("2. User geparst:", user.name);
        return fetch(`/api/posts?userId=${user.id}`);
    })
    .then(response => {
        console.log("3. Posts-Response erhalten");
        return response.json();
    })
    .then(posts => {
        console.log("4. Posts:", posts.length);
    })
    .catch(error => {
        // Fängt JEDEN Fehler in der Kette!
        console.error("Irgendwo ging was schief:", error);
    });

Werte in der Kette transformieren

Promise.resolve(5)
    .then(x => x * 2)     // 10
    .then(x => x + 3)     // 13
    .then(x => x.toString())  // "13"
    .then(x => console.log(x));  // Ausgabe: "13"

Return nicht vergessen!

// Falsch - Promise wird nicht weitergegeben
fetch("/api")
    .then(response => {
        response.json();  // Return fehlt!
    })
    .then(data => {
        console.log(data);  // undefined!
    });

// Richtig
fetch("/api")
    .then(response => {
        return response.json();
    })
    .then(data => {
        console.log(data);  // Daten!
    });

// Oder kurz mit Arrow
fetch("/api")
    .then(response => response.json())
    .then(data => console.log(data));

Fehlerbehandlung

catch fängt alle Fehler

fetch("/api/users")
    .then(response => {
        if (!response.ok) {
            throw new Error(`HTTP ${response.status}`);
        }
        return response.json();
    })
    .then(users => {
        // Wenn hier ein Fehler passiert...
        return processUsers(users);
    })
    .then(processed => {
        // Oder hier...
        displayUsers(processed);
    })
    .catch(error => {
        // ...wird er HIER gefangen!
        console.error("Fehler in der Kette:", error);
    });

Fehler in der Kette behandeln und weitermachen

fetch("/api/users")
    .then(response => response.json())
    .then(users => {
        throw new Error("Test-Fehler");
    })
    .catch(error => {
        console.log("Fehler gefangen:", error.message);
        return [];  // Fallback-Wert
    })
    .then(users => {
        // Läuft weiter mit leerem Array!
        console.log("Users:", users);  // []
    });

Mehrere catch-Blöcke

fetch("/api")
    .then(response => {
        if (!response.ok) throw new Error("Netzwerk-Fehler");
        return response.json();
    })
    .catch(error => {
        console.log("Netzwerk-Problem:", error);
        throw error;  // Weitergeben
    })
    .then(data => processData(data))
    .catch(error => {
        console.log("Verarbeitungs-Problem:", error);
    });

Promise.all - Parallel ausführen

Warte auf alle Promises:

const promise1 = fetch("/api/users").then(r => r.json());
const promise2 = fetch("/api/posts").then(r => r.json());
const promise3 = fetch("/api/comments").then(r => r.json());

Promise.all([promise1, promise2, promise3])
    .then(([users, posts, comments]) => {
        console.log("Users:", users.length);
        console.log("Posts:", posts.length);
        console.log("Comments:", comments.length);
    })
    .catch(error => {
        // Wenn EINER fehlschlägt, schlägt alles fehl!
        console.error("Mindestens einer fehlgeschlagen:", error);
    });

Praktisches Beispiel: Mehrere User laden

const userIds = [1, 2, 3, 4, 5];

const promises = userIds.map(id =>
    fetch(`/api/users/${id}`).then(r => r.json())
);

Promise.all(promises)
    .then(users => {
        console.log("Alle Users geladen:", users);
    });

Promise.allSettled - Alle Ergebnisse

Warte auf alle, auch bei Fehlern:

const promises = [
    fetch("/api/users").then(r => r.json()),
    fetch("/api/invalid").then(r => r.json()),  // Wird fehlschlagen
    fetch("/api/posts").then(r => r.json())
];

Promise.allSettled(promises)
    .then(results => {
        results.forEach((result, index) => {
            if (result.status === "fulfilled") {
                console.log(`Promise ${index}: Erfolg`, result.value);
            } else {
                console.log(`Promise ${index}: Fehler`, result.reason);
            }
        });
    });

// Nur erfolgreiche extrahieren
Promise.allSettled(promises)
    .then(results =>
        results
            .filter(r => r.status === "fulfilled")
            .map(r => r.value)
    );

Promise.race - Der Schnellste gewinnt

const schnell = new Promise(resolve =>
    setTimeout(() => resolve("Schnell!"), 100)
);

const langsam = new Promise(resolve =>
    setTimeout(() => resolve("Langsam..."), 1000)
);

Promise.race([schnell, langsam])
    .then(result => console.log(result));  // "Schnell!"

Timeout implementieren

function fetchWithTimeout(url, timeout = 5000) {
    const fetchPromise = fetch(url);

    const timeoutPromise = new Promise((_, reject) => {
        setTimeout(() => reject(new Error("Timeout!")), timeout);
    });

    return Promise.race([fetchPromise, timeoutPromise]);
}

// Verwendung
fetchWithTimeout("/api/data", 3000)
    .then(response => response.json())
    .catch(error => console.log(error.message));  // "Timeout!" wenn zu langsam

Promise.any - Erster Erfolg

const promises = [
    fetch("https://server1.com/api").then(r => r.json()),
    fetch("https://server2.com/api").then(r => r.json()),
    fetch("https://server3.com/api").then(r => r.json())
];

Promise.any(promises)
    .then(result => {
        console.log("Erster erfolgreicher:", result);
    })
    .catch(error => {
        // Nur wenn ALLE fehlschlagen
        console.log("Alle fehlgeschlagen");
    });

Promises aus Callbacks erstellen

// Alte Callback-API
function alteApi(callback) {
    setTimeout(() => callback(null, "Daten"), 1000);
}

// Promise-Wrapper
function neueApi() {
    return new Promise((resolve, reject) => {
        alteApi((error, daten) => {
            if (error) {
                reject(error);
            } else {
                resolve(daten);
            }
        });
    });
}

// Jetzt mit then/catch nutzbar
neueApi().then(daten => console.log(daten));

Utility: promisify

function promisify(fn) {
    return function(...args) {
        return new Promise((resolve, reject) => {
            fn(...args, (error, result) => {
                if (error) reject(error);
                else resolve(result);
            });
        });
    };
}

// Verwendung
const readFileAsync = promisify(fs.readFile);
readFileAsync("file.txt", "utf8").then(content => console.log(content));

Häufige Fehler

1. Promise nicht zurückgeben

// Falsch
function getData() {
    fetch("/api").then(r => r.json());  // Promise "verschwindet"
}

// Richtig
function getData() {
    return fetch("/api").then(r => r.json());
}

2. Verschachtelte Promises

// Falsch - Verschachtelt
fetch("/api/user")
    .then(response => {
        response.json().then(user => {
            fetch(`/api/posts/${user.id}`).then(response => {
                // ... Callback Hell mit Promises!
            });
        });
    });

// Richtig - Flache Kette
fetch("/api/user")
    .then(response => response.json())
    .then(user => fetch(`/api/posts/${user.id}`))
    .then(response => response.json())
    .then(posts => console.log(posts));

3. Fehler ignorieren

// Falsch - Unhandled Promise Rejection
fetch("/api").then(r => r.json());

// Richtig - Immer catch!
fetch("/api")
    .then(r => r.json())
    .catch(e => console.error(e));

Zusammenfassung

MethodeBeschreibung
new Promise()Promise erstellen
.then()Bei Erfolg
.catch()Bei Fehler
.finally()Immer ausführen
Promise.all()Alle parallel, fail-fast
Promise.allSettled()Alle Ergebnisse
Promise.race()Schnellster
Promise.any()Erster Erfolg
Promise.resolve()Sofort fulfilled
Promise.reject()Sofort rejected

Übung: Baue eine retry Funktion mit Promises: retry(asyncFn, maxAttempts) soll bis zu 3 Mal versuchen, bei Fehler erneut probieren.