Promises stellen einen bedeutenden Fortschritt in der Handhabung asynchroner Operationen in JavaScript dar. Sie wurden mit ECMAScript 6 (ES2015) standardisiert und bieten eine elegantere Alternative zu verschachtelten Callbacks. In diesem Abschnitt werden wir das Konzept, die Funktionsweise und die praktischen Anwendungen von Promises detailliert betrachten.
Ein Promise ist ein Objekt, das einen eventuellen Abschluss (oder Fehlschlag) einer asynchronen Operation und deren resultierenden Wert repräsentiert. Es dient als Stellvertreter für einen Wert, der zum Zeitpunkt der Promise-Erstellung möglicherweise noch nicht bekannt ist.
Ein Promise kann sich in einem von drei Zuständen befinden:
Die Erstellung eines Promise erfolgt über den Promise-Konstruktor,
der eine Funktion (den “Executor”) mit zwei Parametern erwartet:
resolve und reject:
const meinPromise = new Promise((resolve, reject) => {
// Asynchrone Operation
const erfolgreich = true;
if (erfolgreich) {
resolve('Operation erfolgreich abgeschlossen!');
} else {
reject(new Error('Operation fehlgeschlagen.'));
}
});resolve wird aufgerufen, wenn die asynchrone Operation
erfolgreich abgeschlossen wurdereject wird aufgerufen, wenn die Operation
fehlgeschlagen istEin Promise bietet drei Hauptmethoden zur Interaktion:
meinPromise
.then((ergebnis) => {
console.log('Erfolg:', ergebnis);
return 'Neuer Wert'; // Kann für Chaining verwendet werden
})
.catch((fehler) => {
console.error('Fehler:', fehler);
throw new Error('Weiterer Fehler'); // Oder einen neuen Fehler werfen
})
.finally(() => {
console.log('Diese Ausführung erfolgt immer, unabhängig vom Ergebnis');
});.then() wird aufgerufen, wenn das Promise erfüllt
wurde.catch() wird aufgerufen, wenn das Promise abgelehnt
wurde.finally() wird immer aufgerufen, unabhängig vom
AusgangHier ein Beispiel, das die Verwendung von Promises für einen Datenabruf demonstriert:
function holeDatenVomServer(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(new Error(`Anfrage fehlgeschlagen mit Status ${xhr.status}`));
}
};
xhr.onerror = function() {
reject(new Error('Netzwerkfehler aufgetreten'));
};
xhr.send();
});
}
holeDatenVomServer('https://api.beispiel.de/benutzer/1')
.then(daten => {
console.log('Benutzerdaten erfolgreich geladen:', daten);
})
.catch(fehler => {
console.error('Fehler beim Laden der Daten:', fehler);
});Einer der größten Vorteile von Promises ist die Möglichkeit der Verkettung (Chaining). Dies ermöglicht es, mehrere asynchrone Operationen in einer linearen, leicht lesbaren Weise zu verknüpfen:
function holeBenutzerId() {
return new Promise((resolve) => {
setTimeout(() => resolve(42), 1000);
});
}
function holeBenutzerDetails(id) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
id: id,
name: 'Max Mustermann',
email: 'max@beispiel.de'
});
}, 1000);
});
}
function holeBenutzerPosts(benutzer) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({
benutzer: benutzer,
posts: [
{ id: 1, titel: 'Erster Beitrag' },
{ id: 2, titel: 'Zweiter Beitrag' }
]
});
}, 1000);
});
}
// Promise-Chaining
holeBenutzerId()
.then(id => {
console.log('Benutzer-ID erhalten:', id);
return holeBenutzerDetails(id);
})
.then(benutzer => {
console.log('Benutzerdetails erhalten:', benutzer);
return holeBenutzerPosts(benutzer);
})
.then(daten => {
console.log('Benutzer mit Posts:', daten);
})
.catch(fehler => {
console.error('Ein Fehler ist aufgetreten:', fehler);
});Jeder .then()-Aufruf:
Dies vermeidet die “Callback-Hölle” und sorgt für einen linearen, leichter lesbaren Code.
Ein weiterer großer Vorteil von Promises ist die zentrale
Fehlerbehandlung. Fehler (Exceptions) in einer Promise-Kette werden
durch die Kette “durchgereicht”, bis sie von einem
.catch()-Handler abgefangen werden:
holeBenutzerId()
.then(id => {
if (id <= 0) {
throw new Error('Ungültige Benutzer-ID');
}
return holeBenutzerDetails(id);
})
.then(benutzer => {
console.log('Benutzerdetails:', benutzer);
return holeBenutzerPosts(benutzer);
})
.catch(fehler => {
// Fängt Fehler aus ALLEN vorherigen Promises ab
console.error('Fehler in der Promise-Kette:', fehler);
})
.then(() => {
console.log('Diese Ausführung erfolgt auch nach einem abgefangenen Fehler');
});Wenn ein Fehler in einem beliebigen .then()-Block
auftritt (entweder durch das Werfen einer Exception oder durch die
Ablehnung eines zurückgegebenen Promise), wird die Verarbeitung zum
nächsten .catch()-Handler weitergeleitet.
In komplexeren Szenarien kann es sinnvoll sein, verschiedene Fehler unterschiedlich zu behandeln:
function verarbeiteDaten() {
return holeDatenVomServer('https://api.beispiel.de/daten')
.then(daten => {
// Datenverarbeitung
return transformiereDaten(daten);
})
.catch(fehler => {
if (fehler.name === 'NetworkError') {
console.error('Netzwerkfehler aufgetreten, versuche lokalen Cache');
return holeDatenAusCache();
}
if (fehler.name === 'ValidationError') {
console.error('Validierungsfehler:', fehler.message);
return []; // Leeres Array als Fallback
}
// Andere Fehler weiterleiten
throw fehler;
})
.then(daten => {
// Weitere Verarbeitung mit den Daten (entweder vom Server oder aus dem Cache)
return berechneFinalenWert(daten);
})
.catch(fehler => {
// Fängt alle nicht behandelten Fehler ab
console.error('Unerwarteter Fehler:', fehler);
return null; // Fallback-Rückgabewert
});
}Promise bietet statische Methoden zur Verarbeitung mehrerer Promises:
const promise1 = holeDatenVomServer('https://api.beispiel.de/benutzer');
const promise2 = holeDatenVomServer('https://api.beispiel.de/produkte');
const promise3 = holeDatenVomServer('https://api.beispiel.de/bestellungen');
Promise.all([promise1, promise2, promise3])
.then(([benutzer, produkte, bestellungen]) => {
console.log('Alle Daten erfolgreich geladen:');
console.log('Benutzer:', benutzer);
console.log('Produkte:', produkte);
console.log('Bestellungen:', bestellungen);
})
.catch(fehler => {
// Wird aufgerufen, sobald ein Promise fehlschlägt
console.error('Mindestens eine Anfrage ist fehlgeschlagen:', fehler);
});Promise.all() nimmt ein Array von Promises entgegen und
gibt ein Promise zurück, das: - erfüllt wird mit einem Array aller
Ergebnisse, wenn alle Promises erfolgreich sind - abgelehnt wird mit dem
ersten aufgetretenen Fehler, wenn mindestens ein Promise fehlschlägt
const serverA = holeDatenVomServer('https://serverA.beispiel.de/daten');
const serverB = holeDatenVomServer('https://serverB.beispiel.de/daten');
Promise.race([serverA, serverB])
.then(ergebnis => {
console.log('Der schnellere Server hat geantwortet:', ergebnis);
})
.catch(fehler => {
console.error('Der schnellere Server hatte einen Fehler:', fehler);
});Promise.race() nimmt ein Array von Promises entgegen und
gibt ein Promise zurück, das aufgelöst oder abgelehnt wird, sobald einer
der Promises aufgelöst oder abgelehnt wird.
const anfragen = [
holeDatenVomServer('https://api.beispiel.de/benutzer'),
holeDatenVomServer('https://api.beispiel.de/produkte'),
holeDatenVomServer('https://fehlerhafte-url.de/daten')
];
Promise.allSettled(anfragen)
.then(ergebnisse => {
ergebnisse.forEach((ergebnis, index) => {
if (ergebnis.status === 'fulfilled') {
console.log(`Anfrage ${index + 1} erfolgreich:`, ergebnis.value);
} else {
console.log(`Anfrage ${index + 1} fehlgeschlagen:`, ergebnis.reason);
}
});
});Promise.allSettled() wartet, bis alle Promises
abgeschlossen sind (erfolgreich oder fehlgeschlagen) und liefert für
jedes Promise ein Statusobjekt.
const server1 = holeDatenVomServer('https://server1.beispiel.de/daten');
const server2 = holeDatenVomServer('https://server2.beispiel.de/daten');
const server3 = holeDatenVomServer('https://server3.beispiel.de/daten');
Promise.any([server1, server2, server3])
.then(erstesErfolgreichesErgebnis => {
console.log('Mindestens ein Server hat erfolgreich geantwortet:', erstesErfolgreichesErgebnis);
})
.catch(fehler => {
console.error('Alle Server haben versagt:', fehler);
// fehler ist ein AggregateError mit einer .errors-Eigenschaft
fehler.errors.forEach((e, i) => {
console.error(`Fehler von Server ${i + 1}:`, e);
});
});Promise.any() gibt das erste erfolgreich aufgelöste
Promise zurück und ignoriert Fehler, solange mindestens ein Promise
erfolgreich ist. Wenn alle Promises fehlschlagen, gibt es einen
AggregateError zurück.
// Ein sofort aufgelöstes Promise erstellen
const sofortAufgelöst = Promise.resolve('Direktes Ergebnis');
// Ein sofort abgelehntes Promise erstellen
const sofortAbgelehnt = Promise.reject(new Error('Direkter Fehler'));
// Eine Verzögerung mit einem Promise umsetzen
function warte(millisekunden) {
return new Promise(resolve => {
setTimeout(resolve, millisekunden);
});
}
// Verwendung:
warte(2000)
.then(() => console.log('2 Sekunden sind vergangen'));// Callback-basierte Funktion
function traditionelleAsyncFunktion(wert, callback) {
setTimeout(() => {
if (wert < 0) {
callback(new Error('Negativer Wert nicht erlaubt'));
} else {
callback(null, wert * 2);
}
}, 1000);
}
// Promise-Wrapper (Promisification)
function promisifiedFunktion(wert) {
return new Promise((resolve, reject) => {
traditionelleAsyncFunktion(wert, (fehler, ergebnis) => {
if (fehler) {
reject(fehler);
} else {
resolve(ergebnis);
}
});
});
}
// Verwendung der promisifizierten Funktion
promisifiedFunktion(10)
.then(ergebnis => console.log('Ergebnis:', ergebnis))
.catch(fehler => console.error('Fehler:', fehler));// Ein Array von IDs
const benutzerIds = [1, 2, 3, 4, 5];
// Sequentielles Verarbeiten eines Arrays mit Promises
benutzerIds
.reduce((promiseKette, id) => {
return promiseKette
.then(akkumulator => {
return holeDatenVomServer(`https://api.beispiel.de/benutzer/${id}`)
.then(benutzerDaten => {
akkumulator.push(benutzerDaten);
return akkumulator;
});
});
}, Promise.resolve([]))
.then(alleBenutzerDaten => {
console.log('Alle Benutzerdaten sequentiell geladen:', alleBenutzerDaten);
})
.catch(fehler => {
console.error('Fehler beim Laden der Benutzerdaten:', fehler);
});function mapAsync(array, asyncFunktion) {
return Promise.all(array.map(item => asyncFunktion(item)));
}
// Beispielverwendung
mapAsync(benutzerIds, id => holeDatenVomServer(`https://api.beispiel.de/benutzer/${id}`))
.then(benutzerArray => {
console.log('Alle Benutzer parallel geladen:', benutzerArray);
})
.catch(fehler => {
console.error('Fehler beim parallelen Laden:', fehler);
});async function limitParallelAsync(array, asyncFunktion, limit) {
const results = [];
const executing = [];
for (const [index, item] of array.entries()) {
const p = Promise.resolve().then(() => asyncFunktion(item, index, array));
results.push(p);
if (limit <= array.length) {
const e = p.then(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
}
return Promise.all(results);
}
// Beispielverwendung: Maximal 3 parallele Anfragen
limitParallelAsync(benutzerIds, id => holeDatenVomServer(`https://api.beispiel.de/benutzer/${id}`), 3)
.then(ergebnisse => {
console.log('Alle Anfragen abgeschlossen:', ergebnisse);
});Ein häufig benötigtes Muster ist die Implementierung eines Timeouts für Promises:
function mitTimeout(promise, millisekunden) {
const timeout = new Promise((_, reject) => {
const id = setTimeout(() => {
clearTimeout(id);
reject(new Error(`Timeout nach ${millisekunden} ms`));
}, millisekunden);
});
return Promise.race([promise, timeout]);
}
// Verwendung
mitTimeout(holeDatenVomServer('https://api.beispiel.de/daten'), 5000)
.then(daten => {
console.log('Daten rechtzeitig erhalten:', daten);
})
.catch(fehler => {
if (fehler.message.includes('Timeout')) {
console.error('Die Anfrage hat zu lange gedauert.');
} else {
console.error('Ein anderer Fehler ist aufgetreten:', fehler);
}
});Viele moderne Browser-APIs geben direkt Promises zurück:
// Fetch API
fetch('https://api.beispiel.de/daten')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP-Fehler: ${response.status}`);
}
return response.json();
})
.then(daten => {
console.log('Daten:', daten);
})
.catch(fehler => {
console.error('Fehler beim Abrufen der Daten:', fehler);
});
// Cache API
caches.open('mein-cache')
.then(cache => {
return cache.add('/styles.css');
})
.then(() => {
console.log('CSS-Datei erfolgreich gecacht');
});
// Web Animation API
document.querySelector('.element').animate(
[{ opacity: 0 }, { opacity: 1 }],
{ duration: 1000 }
).finished
.then(() => {
console.log('Animation abgeschlossen');
});Viele ältere APIs wurden mit Promise-basierten Versionen neu gestaltet:
// IndexedDB via Promise-Wrapper
const dbPromise = window.indexedDB.open('meine-datenbank', 1);
dbPromise
.then(db => {
const tx = db.transaction('kunden', 'readonly');
const store = tx.objectStore('kunden');
return store.get(42);
})
.then(kunde => {
console.log('Kunde gefunden:', kunde);
})
.catch(fehler => {
console.error('Fehler beim Zugriff auf die Datenbank:', fehler);
});// Schlecht - keine Fehlerbehandlung
holeDatenVomServer('https://api.beispiel.de/daten')
.then(daten => {
verarbeiteDaten(daten);
});
// Fehler werden ignoriert und "verschluckt"
// Besser
holeDatenVomServer('https://api.beispiel.de/daten')
.then(daten => {
verarbeiteDaten(daten);
})
.catch(fehler => {
console.error('Fehler beim Laden oder Verarbeiten der Daten:', fehler);
});// Schlecht - "Promise-Hell"
holeDatenVomServer('https://api.beispiel.de/daten')
.then(daten => {
return holeDatenVomServer(`https://api.beispiel.de/details/${daten.id}`)
.then(details => {
return holeDatenVomServer(`https://api.beispiel.de/extras/${details.extraId}`)
.then(extras => {
// Verschachtelte then-Callbacks
return { daten, details, extras };
});
});
})
.catch(fehler => {
console.error('Fehler:', fehler);
});
// Besser - flache Promise-Kette
holeDatenVomServer('https://api.beispiel.de/daten')
.then(daten => {
return holeDatenVomServer(`https://api.beispiel.de/details/${daten.id}`)
.then(details => {
return { daten, details };
});
})
.then(({ daten, details }) => {
return holeDatenVomServer(`https://api.beispiel.de/extras/${details.extraId}`)
.then(extras => {
return { daten, details, extras };
});
})
.catch(fehler => {
console.error('Fehler:', fehler);
});// Schlecht - Promise-Rückgabe fehlt
benutzerEinloggen()
.then(benutzer => {
// Promise-Rückgabe fehlt, die Kette wird unterbrochen
holeDatenVomServer(`https://api.beispiel.de/profil/${benutzer.id}`);
})
.then(profil => {
// 'profil' ist undefined, weil das vorherige Promise nicht zurückgegeben wurde
console.log(profil.name); // TypeError
});
// Besser
benutzerEinloggen()
.then(benutzer => {
// Promise wird korrekt zurückgegeben
return holeDatenVomServer(`https://api.beispiel.de/profil/${benutzer.id}`);
})
.then(profil => {
// 'profil' enthält nun die korrekten Daten
console.log(profil.name);
});