JavaScript Promises
Asynchrone Programmierung mit Promises ist einfacher als gedacht, versprochen!
Wie funktioniert asynchrone Programmierung in JavaScript und wie verwendet man dafür Promises?
Ältere APIs arbeiten Callback-basiert: Man übergibt eine Funktion welche aufgrufen wird, sobald die aysnchrone Operation abgeschlossen wurde (entweder mit Error oder einem Ergebnis).
login(username, pw, (user, error) => {
if (user) {
getRoles(user.id, (roles, error) => {
if (roles) {
updateLastLoggedInDate(user, (error) => {
if (!error) {
redirectToDashboard();
} else {
handleError();
}
});
} else {
handleError(error);
}
});
} else {
handleError(error);
}
});
Ein Objekt, welches asynchron zum Programmablauf Daten zur Verfügung
stellt oder einen Fehler liefert.
Ein Promise kann immer nur einen diese drei Status haben:
Pending
: Promise, welches noch keinen Wert oder Fehler hat.
Fulfilled
: Promise war erfolgreich
Rejected
: Promise hat Fehler verursacht
const promise = new Promise((resolve, reject) => {
// success
resolve('ok');
// error
reject(error);
});
// registering resolve-callback
promise.then(result => {
console.log('result', result);
});
// registering error-callback
promise.catch(error => {
console.log('promise error', error);
});
Jedes Promise beginnt nach der Erstellung sofort mit seiner Arbeit.
Mit then
übergibt man eine Funktion die aufgerufen wird, wenn das Promise erfolgreich ausgeführt wird oder bereits erfolgreich ausgeführt wurde.
Man kann auch öfters then
aufrufen.
catch
ist der Error-Handler. Dieser wird aufgerufen wenn ein Fehler passiert.
Was nicht geht ist resolve()
oder reject()
öfters aufzurufen.
Ein Promise kann nur von Pending -> Fulfilled
oder Pending -> Rejected
überführt werden. Danach ist das Promise abgeschlossen.
Statt eine "Callback Pyramide of Doom" zu bauen sollte man Promise-Chaining nutzen. Ein Ergebnis eines Promises wird als Input zum nächsten Promise weitergereicht.
Jeder Return-Wert im then
steht als neues Promise zur Verfügung:
// login returns a promise which returns a remote user
// when log in succeeds
const loginPromise = login(username, pw);
const userNamePromise = loginPromise.then(user => {
return user.username;
});
userNamePromise.then(name => {
console.log('username is:', name);
});
Diese Promises können nun verkettet werden, kurz chaining
:
login(username, pw)
.then(user => {
return user.username;
})
.then(name => {
console.log('username is:', name);
});
Zusätzlich kann ein Error-Handler angehängt werden, der im Fehlerfall aufgerufen wird:
login(username, pw)
.then(user => {
return user.username;
})
.then(name => {
console.log('username is:', name);
})
.catch(error => {
console.log('Oops! Error: ', error)
});
Mit finally
kann man einen Handler definieren, der immer am Ende der Promise-Chain aufgerufen wird, auch im Fehlerfall:
login(username, pw)
.then(user => {
return user.username;
})
.then(name => {
console.log('username is:', name);
})
.catch(error => {
console.log('Oops! Error: ', error)
})
.finally(() => console.log('Done!'));
Hier ein Beispiel wie man Daten mittels Fetch-API abruft und miteinander verbindet:
// loading all posts
fetch("https://jsonplaceholder.typicode.com/posts")
.then(response => {
// converting response-body to json
return response.json();
})
.then(posts => {
// getting all userIds from posts
return posts.map(post => post.userId);
})
.then(userIds => {
// trick to remove duplicates from array and picking first 3
return Array.from(new Set(userIds)).slice(0, 3);
})
.then(uniqIds => {
// making new requests from id-array:
// promise all loads all desired promises in parallel
// returning all results at once as array.
return Promise.all(
uniqIds.map(id =>
fetch("https://jsonplaceholder.typicode.com/users/" + id)
.then(response => response.json())
)
);
})
.then(users => {
// accessing the resulting users:
console.log('3 users who wrote posts: ', users);
})
.catch(error => {
console.log('Something went wrong:', error);
});
Passiert ein Fehler wird der nächstliegende Error-Handler aufgerufen und die dazwischenliegenden Promises übersprungen:
loadA()
.then(result => {
// error occurs here. division by zero
const someValue = result / 0;
return loadB(someValue);
})
.then(result => loadC(result))
.catch(error => {
console.log("Oh no! Error:", error);
})
.then(() => loadFoobar())
.catch(error => {
console.log("Foobar loading failed! Error:", error);
})
.finally(() => {
proceedToDashboard();
});
.then(result => loadC(result))
wird nie aufgerufen, weil der nächste catch
-Handler in der Chain aufgerufen wird.
Danach wird die Chain aber weiter abgearbeitet, wenn nach dem catch
weitere then
folgen.
finally
wird immer aufgerufen, jedoch ohne Übergabewert.
Funkion welche sofort ein Promise im Status fulfilled
erzeugt mit dem gewünschten Wert:
Promise.resolve('Hello World!')
.then(value => {
console.log('promise resolved to:', value);
});
Funkion welche sofort ein Promise im Status rejected
erzeugt mit dem gewünschten Grund/Fehler:
Promise.reject('NO NO NO!')
.then(value => {
// never called
})
.catch(error => {
console.log('Reason:', error);
});
Funktion welche alle übergebenen Promises gleichzeitig ausführt und alle Ergebnisse in der gleichen Reihenfolge als Array dem nächsten then
-Handler übergibt:
Promise.all([loadA(), loadB(), loadC()]).then(results => {
console.log("A", results[0]);
console.log("B", results[1]);
console.log("C", results[2]);
});
Im Fehlerfall gilt die gesamte Operation als fehlerhaft, nach dem Motto "Alles oder nichts".
Ähnlich wie Promise.all
nur dass ein fehler eines einzelnen Promises nicht zum Abbruch führt.
Die Ergebnise sind mit Meta-Informationen ausgestattet, welche angeben, ob das jeweilige Promise resolved
oder rejected
wurde.
Führt alle Promises gleichzeitig aus, das schnellste Promise gewinnt. Das Ergebnis wird dem nächsten then
-Handler übergeben. Die Ergebnisse der anderen Promises werden ignoriert.
Hier fast eine Stunde Material mit ausführlichen Beispielen und Erklärungen:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
https://flaviocopes.com/javascript-event-loop
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
https://jsonplaceholder.typicode.com/
http://bluebirdjs.com/docs/api/cancellation.html
https://rxjs.dev/
Kommentare