Promises – Bessere Handhabung von asynchronem Code
Die asynchrone Programmierung ist ein wesentlicher Bestandteil von JavaScript, insbesondere bei Operationen wie Netzwerkaufrufen, Dateisystemzugriffen oder Timern. Vor der Einführung von Promises in ES6 (ECMAScript 2015) wurden asynchrone Operationen hauptsächlich mit Callbacks gehandhabt, was oft zu unübersichtlichem und schwer wartbarem Code führte (bekannt als “Callback Hell”). Promises bieten eine elegantere und strukturiertere Methode, um mit asynchronem Code umzugehen.
Was sind Promises?
Ein Promise ist ein Objekt, das einen Wert repräsentiert, der jetzt, später oder nie verfügbar sein wird. Es handelt sich um einen Platzhalter für das Ergebnis einer asynchronen Operation. Ein Promise kann sich in einem von drei Zuständen befinden:
- Pending (ausstehend): Die Operation ist noch nicht abgeschlossen.
- Fulfilled (erfüllt): Die Operation wurde erfolgreich abgeschlossen, und ein Wert ist verfügbar.
- Rejected (abgelehnt): Die Operation ist fehlgeschlagen, und ein Fehler ist verfügbar.
Ein einfaches Beispiel
Angenommen, wir haben eine Funktion, die Daten von einer API abruft:
function fetchData(callback) {
setTimeout(() => {
const data = { name: 'Max', age: 30 };
callback(data);
}, 1000);
}
fetchData((data) => {
console.log(data);
});
Mit Callbacks kann dieser Code bei komplexeren Szenarien schnell unübersichtlich werden.
Mit Promises können wir denselben Code strukturierter schreiben:
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
const data = { name: 'Max', age: 30 };
resolve(data);
// Bei einem Fehler würde man reject(error) aufrufen
}, 1000);
});
}
fetchData()
.then((data) => {
console.log(data); // { name: 'Max', age: 30 }
})
.catch((error) => {
console.error('Fehler:', error);
});
Aufbau eines Promises
Ein Promise wird mit dem new Promise
Konstruktor erstellt, der eine Funktion (executor
) akzeptiert. Diese Funktion erhält zwei Argumente:
resolve
: Wird aufgerufen, wenn die asynchrone Operation erfolgreich war.reject
: Wird aufgerufen, wenn die asynchrone Operation fehlgeschlagen ist.
Beispiel:
const myPromise = new Promise((resolve, reject) => {
// Asynchrone Operation
const success = true;
if (success) {
resolve('Operation erfolgreich!');
} else {
reject('Operation fehlgeschlagen!');
}
});
Verwendung von then
und catch
Promises bieten die Methoden then
und catch
, um auf die Auflösung oder Ablehnung zu reagieren.
then
wird aufgerufen, wenn das Promise erfüllt ist.catch
wird aufgerufen, wenn das Promise abgelehnt wurde.
Beispiel:
myPromise
.then((message) => {
console.log(message); // 'Operation erfolgreich!'
})
.catch((error) => {
console.error(error); // 'Operation fehlgeschlagen!'
});
Verkettung von Promises
Einer der großen Vorteile von Promises ist die Möglichkeit, sie zu verketten. Das ermöglicht das sequentielle Ausführen von asynchronen Operationen.
Beispiel:
function firstOperation() {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Erste Operation abgeschlossen.');
resolve(1);
}, 1000);
});
}
function secondOperation(result) {
return new Promise((resolve) => {
setTimeout(() => {
console.log('Zweite Operation abgeschlossen.');
resolve(result + 1);
}, 1000);
});
}
firstOperation()
.then((result) => {
return secondOperation(result);
})
.then((finalResult) => {
console.log('Endergebnis:', finalResult); // Endergebnis: 2
});
Parallelisierung mit Promise.all
Mit Promise.all
können mehrere Promises parallel ausgeführt werden. Die Methode wartet, bis alle Promises erfüllt sind, oder gibt einen Fehler zurück, wenn eines fehlschlägt.
Beispiel:
const promise1 = new Promise((resolve) => setTimeout(() => resolve('Ergebnis 1'), 1000));
const promise2 = new Promise((resolve) => setTimeout(() => resolve('Ergebnis 2'), 2000));
Promise.all([promise1, promise2])
.then((results) => {
console.log(results); // ['Ergebnis 1', 'Ergebnis 2']
})
.catch((error) => {
console.error('Fehler:', error);
});
Fehlerbehandlung
Die Fehlerbehandlung ist mit Promises deutlich verbessert. Du kannst catch
verwenden, um Fehler abzufangen, und diese Behandlung kann über mehrere verkettete then
-Aufrufe hinweg erfolgen.
Beispiel:
fetchData()
.then((data) => {
console.log('Daten erhalten:', data);
return processData(data);
})
.then((processedData) => {
console.log('Verarbeitete Daten:', processedData);
})
.catch((error) => {
console.error('Es ist ein Fehler aufgetreten:', error);
});
Wenn in irgendeinem der vorherigen Schritte ein Fehler auftritt, wird der catch
-Block ausgeführt.
Promise-Methoden
Zusätzlich zu Promise.all
gibt es weitere nützliche Methoden:
Promise.race(iterable)
: Gibt das erste Promise zurück, das erfüllt oder abgelehnt wird.Promise.allSettled(iterable)
: Wartet, bis alle Promises entweder erfüllt oder abgelehnt sind, und gibt ein Array von Objekten mit dem Status und dem Wert/Grund zurück.Promise.any(iterable)
: Gibt das erste erfüllte Promise zurück oder einen AggregateError, wenn alle Promises abgelehnt wurden.
Beispiel mit Promise.race
:
const promise1 = new Promise((resolve) => setTimeout(() => resolve('Schnell'), 500));
const promise2 = new Promise((resolve) => setTimeout(() => resolve('Langsam'), 1000));
Promise.race([promise1, promise2])
.then((result) => {
console.log(result); // 'Schnell'
});
Fazit
Promises haben die Art und Weise revolutioniert, wie wir asynchronen Code in JavaScript schreiben. Sie bieten eine klare und strukturierte Methode zur Handhabung von asynchronen Operationen, erleichtern die Fehlerbehandlung und verbessern die Lesbarkeit des Codes. Durch das Verständnis und die effektive Nutzung von Promises kannst du robustere und wartbarere Anwendungen entwickeln.
Beginne noch heute, Promises in deinem Code zu verwenden, und erlebe den Unterschied in der Qualität und Wartbarkeit deines Codes!