Promises-Probleme können JavaScript-Projekte ausbremsen.DC Studio | shutterstock.com Das Thema Promises in JavaScript haben wir bereits in einem ausführlichen Tutorial beleuchtet. In diesem Artikel werfen wir einen Blick auf vier häufige Probleme im Zusammenhang mit Promises – und darauf, wie diese sich am besten auflösen lassen. 1. Handler, die Promises zurückgeben Werden Informationen aus einem then– oder catch-Handler zurückgeben, werden diese stets mit einem Promise “gewrappt” (um/eingeschlossen) – sofern es sich nicht bereits um ein Promise handelt. Code wie den folgenden müssen Entwickler also nicht schreiben: firstAjaxCall.then(() => { return new Promise((resolve, reject) => { nextAjaxCall().then(() => resolve()); }); }); Weil nextAjaxCall ebenfalls ein Promise zurückgibt, können Sie sich stattdessen beschränken auf: firstAjaxCall.then(() => { return nextAjaxCall(); }); Wird darüber hinaus ein einfacher Wert (kein Promise) zurückgegeben, gibt der Handler ein Promise zurück, das auf diesen Wert (Value) aufgelöst wird. Sie können also weiterhin .then nutzen, um die Ergebnisse zu callen: firstAjaxCall.then((response) => { return response.importantField }).then((resolvedValue) => { // resolvedValue is the value of response.importantField returned above console.log(resolvedValue); }); Das ist praktisch – wirft aber die Frage auf, wie zu verfahren ist, wenn Sie den State eines eingehenden Werts (Values) nicht kennen. Lösungstipp: Promise.resolve() nutzen Wenn Sie sich unsicher sind, ob Ihr eingehender Wert bereits ein Promise ist oder nicht, können Sie einfach auf die statische Promise.resolve()-Methode zurückgreifen. Betreffende Variablen können als Argument an Promise.resolve übergeben werden. Handelt es sich um ein Promise, wird dieses von der Methode zurückgegeben. Handelt es sich bei der Variable hingegen um einen Value, gibt die Methode ein Promise zurück, das auf den Wert aufgelöst wurde: let processInput = (maybePromise) => { let definitelyPromise = Promise.resolve(maybePromise); definitelyPromise.then(doSomeWork); }; 2. .then nimmt stets eine Funktion Sehr wahrscheinlich haben schon einmal Promise-Code gesehen (oder geschrieben). Dieser sieht in etwa so aus: let getAllArticles = () => { return someAjax.get(‘/articles’); }; let getArticleById = (id) => { return someAjax.get(`/articles/${id}`); }; getAllArticles().then(getArticleById(2)); Dieser Code soll den Zweck erfüllen, zunächst alle Artikel abzurufen – und anschließend den mit der ID 2. Auch wenn die Intention war, diese Aktionen sequenziell auszuführen, werden die beiden Promises im Wesentlichen zur selben Zeit gestartet. Die Reihenfolge ist also dem Zufall überlassen. Das Problem: Bei diesem Code haben wir eine JavaScript-Grundregel außer Acht gelassen: Argumente für Funktionen werden immer ausgewertet, bevor sie an die Funktion übergeben werden. In unserem Fall geht .then aber keine Funktion zu, sondern der Rückgabewert von getArticleById. Das liegt daran, dass getArticleById direkt mit dem Klammeroperator aufgerufen wird. Um das zu beheben, gibt es mehrere Möglichkeiten. Lösungstipp 1: Arrow-Funktion als Wrapper nutzen Um sicherzustellen, dass beide Funktionen nacheinander ausgeführt werden, könnten Sie folgendermaßen verfahren: // A little arrow function is all you need getAllArticles().then(() => getArticleById(2)); Indem Sie den Call von getArticleById mit einer Arrow-Funktion umschliessen, wird .then mit einer Funktion “versorgt”, die es aufrufen kann, wenn getAllArticles() aufgelöst ist. Lösungstipp 2: Benannte Funktionen an .then übergeben Sie müssen jedoch nicht auf anonyme Inline-Funktionen als Argumente für .then zurückgreifen. Stattdessen können Sie einfach einer Variable eine Funktion zuweisen und die Referenz auf diese an .then übergeben. // function definitions from Gotcha #2 let getArticle2 = () => { return getArticleById(2); }; getAllArticles().then(getArticle2); getAllArticles().then(getArticle2); In diesem Beispiel übergeben wir die Referenz lediglich und rufen sie nicht auf. Lösungstipp 3: async/await verwenden Eine weitere Möglichkeit, die Reihenfolge der Ereignisse klarer zu gestalten, besteht darin, die Keywords async/await zu nutzen: async function getSequentially() { const allArticles = await getAllArticles(); // Wait for first call const specificArticle = await getArticleById(2); // Then wait for second // … use specificArticle } Das legt eindeutig und offensichtlich fest, dass zwei Schritte nacheinander ausgeführt werden – und der Execution-Prozess erst dann weiterläuft, wenn beide abgeschlossen sind. 3. Nicht-funktionale .then-Argumente Das eben behandelte Problem lässt sich noch erweitern: let getAllArticles = () => { return someAjax.get(‘/articles’); }; let getArticleById = (id) => { return someAjax.get(`/articles/${id}`); }; getAllArticles().then(getArticleById(2)).then((article2) => { // Do something with article2 }); Wir wissen bereits, dass diese Kette nicht wie gewünscht sequenziell ausgeführt wird – haben jetzt aber ein zusätzliches Promise-Problem: Was ist der Wert von article2 im letzten .then? Da wir keine Funktion an das erste Argument von .then übergeben, gibt JavaScript das ursprüngliche Promise mit seinem aufgelösten Wert weiter. Das führt dazu, dass der Value von article2 dem Wert entspricht, den getAllArticles() aufgelöst hat. Wenn Sie es mit einer langen Kette von .then-Methoden zu tun haben und einige Ihrer Handler Werte aus früheren .then-Methoden erhalten, sollten Sie sicherstellen, dass auch tatsächlich Funktionen an .then übergeben werden. Lösungstipp 1: Benannte Funktionen mit formalen Parametern nutzen Eine Möglichkeit, dieses Problem zu händeln, besteht darin, benannte Funktionen zu übergeben, die einen einzelnen, formalen Parameter definieren (also ein Argument entgegennehmen). Das ermöglicht Ihnen, generische Funktionen zu erstellen, die sich sowohl innerhalb als auch außerhalb einer Kette von .then-Methoden nutzen lassen. Angenommen, die Funktion getFirstArticle ruft eine API auf, um den neuesten article in einem Set abzurufen und diesen zu einem article-Objekt aufzulösen – mit Eigenschaften wie ID, Titel und Publikationsdatum. Eine weitere Funktion – getCommentsForArticleId – nimmt eine Artikel-ID entgegen und führt einen API-Call aus, um sämtliche Kommentare zu diesem Artikel abzurufen. Um diese beiden Funktionen miteinander zu verbinden, müssen Sie nun nur noch vom Auflösungswert der ersten Funktion (ein article-Objekt) zum erwarteten Argumentwert der zweiten Funktion (eine Artikel-ID) gelangen. Zu diesem Zweck könnten Sie auf eine anonyme Inline-Funktion zurückgreifen: getFirstArticle().then((article) => { return getCommentsForArticleId(article.id); }); Oder Sie erstellen eine simple Funktion, die einen Artikel entgegennimmt, dessen ID zurückgibt und alles mit .then verknüpft: let extractId = (article) => article.id; getFirstArticle().then(extractId).then(getCommentsForArticleId); Diese Lösung verschleiert den Auflösungswert aller Funktionen, weil diese nicht inline definiert sind. Andererseits entstehen dadurch flexible Funktionen, die sehr wahrscheinlich wiederverwendet werden können. Hier gibt es auch ein “Wiedersehen” mit einem Learning aus unserem ersten behandelten Problem: Zwar gibt extractId kein Promise zurück, dennoch wird .then seinen Rückgabewert in eines verpacken – wodurch Sie .then erneut callen können. Lösungstipp 2: async/await verwenden Auch mit async/await lässt sich an dieser Stelle Abhilfe schaffen – beziehungsweise mehr Übersichtlichkeit herstellen: async function getArticleAndComments() { const article = await getFirstArticle(); const comments = await getCommentsForArticleId(article.id); // Extract ID directly // … use comments } Hier warten Sie einfach, bis getFirstArticle() beendet ist und verwenden dann den Artikel, um seine ID abzurufen. Das ist möglich, weil sicher ist, dass der Artikel durch die zugrundeliegende Operation aufgelöst wurde. 4. async/await beeinträchtigt Concurrency Falls Sie mehrere asynchrone Prozesse gleichzeitig starten möchten, setzen Sie diese in einen Loop und nutzen await: // (Bad practice below!) async function getMultipleUsersSequentially(userIds) { const users = []; const startTime = Date.now(); for (const id of userIds) { // await pauses the *entire loop* for each fetch const user = await fetchUserDataPromise(id); users.push(user); } const endTime = Date.now(); console.log(`Sequential fetch took ${endTime – startTime}ms`); return users; } // If each fetch takes 1.5s, 3 fetches would take ~4.5s total. In diesem Beispiel wollen wir sämtliche fetchUserDataPromise()-Requests zusammen senden. Allerdings wird im Loop dennoch eine Anfrage nach der anderen ausgeführt. Lösungstipp: Promise.all verwenden Diese Problemstellung lässt sich ganz einfach auflösen – und zwar mit Hilfe von Promise.all: // (Requests happen concurrently) async function getMultipleUsersConcurrently(userIds) { console.log(“Starting concurrent fetch…”); const startTime = Date.now(); const promises = userIds.map(id => fetchUserDataPromise(id)); const users = await Promise.all(promises); const endTime = Date.now(); console.log(`Concurrent fetch took ${endTime – startTime}ms`); return users; } // If each fetch takes 1.5s, 3 concurrent fetches would take ~1.5s total (plus a tiny overhead). Dabei bewirkt Promise.all dass sämtliche Promises im Array gleichzeitig gestartet und abgeschlossen werden. In diesem Anwendungsfall sind Promises im Vergleich zu async/await die simplere Lösung (dennoch kommt await zum Einsatz, um darauf zu warten, dass Promise.all abgeschlossen wird). (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox!
Promise-Probleme in JavaScript beheben
Promises-Probleme können JavaScript-Projekte ausbremsen.DC Studio | shutterstock.com Das Thema Promises in JavaScript haben wir bereits in einem ausführlichen Tutorial beleuchtet. In diesem Artikel werfen wir einen Blick auf vier häufige Probleme im Zusammenhang mit Promises – und darauf, wie diese sich am besten auflösen lassen. 1. Handler, die Promises zurückgeben Werden Informationen aus einem then– oder catch-Handler zurückgeben, werden diese stets mit einem Promise “gewrappt” (um/eingeschlossen) – sofern es sich nicht bereits um ein Promise handelt. Code wie den folgenden müssen Entwickler also nicht schreiben: firstAjaxCall.then(() => { return new Promise((resolve, reject) => { nextAjaxCall().then(() => resolve()); }); }); Weil nextAjaxCall ebenfalls ein Promise zurückgibt, können Sie sich stattdessen beschränken auf: firstAjaxCall.then(() => { return nextAjaxCall(); }); Wird darüber hinaus ein einfacher Wert (kein Promise) zurückgegeben, gibt der Handler ein Promise zurück, das auf diesen Wert (Value) aufgelöst wird. Sie können also weiterhin .then nutzen, um die Ergebnisse zu callen: firstAjaxCall.then((response) => { return response.importantField }).then((resolvedValue) => { // resolvedValue is the value of response.importantField returned above console.log(resolvedValue); }); Das ist praktisch – wirft aber die Frage auf, wie zu verfahren ist, wenn Sie den State eines eingehenden Werts (Values) nicht kennen. Lösungstipp: Promise.resolve() nutzen Wenn Sie sich unsicher sind, ob Ihr eingehender Wert bereits ein Promise ist oder nicht, können Sie einfach auf die statische Promise.resolve()-Methode zurückgreifen. Betreffende Variablen können als Argument an Promise.resolve übergeben werden. Handelt es sich um ein Promise, wird dieses von der Methode zurückgegeben. Handelt es sich bei der Variable hingegen um einen Value, gibt die Methode ein Promise zurück, das auf den Wert aufgelöst wurde: let processInput = (maybePromise) => { let definitelyPromise = Promise.resolve(maybePromise); definitelyPromise.then(doSomeWork); }; 2. .then nimmt stets eine Funktion Sehr wahrscheinlich haben schon einmal Promise-Code gesehen (oder geschrieben). Dieser sieht in etwa so aus: let getAllArticles = () => { return someAjax.get('/articles'); }; let getArticleById = (id) => { return someAjax.get(`/articles/${id}`); }; getAllArticles().then(getArticleById(2)); Dieser Code soll den Zweck erfüllen, zunächst alle Artikel abzurufen – und anschließend den mit der ID 2. Auch wenn die Intention war, diese Aktionen sequenziell auszuführen, werden die beiden Promises im Wesentlichen zur selben Zeit gestartet. Die Reihenfolge ist also dem Zufall überlassen. Das Problem: Bei diesem Code haben wir eine JavaScript-Grundregel außer Acht gelassen: Argumente für Funktionen werden immer ausgewertet, bevor sie an die Funktion übergeben werden. In unserem Fall geht .then aber keine Funktion zu, sondern der Rückgabewert von getArticleById. Das liegt daran, dass getArticleById direkt mit dem Klammeroperator aufgerufen wird. Um das zu beheben, gibt es mehrere Möglichkeiten. Lösungstipp 1: Arrow-Funktion als Wrapper nutzen Um sicherzustellen, dass beide Funktionen nacheinander ausgeführt werden, könnten Sie folgendermaßen verfahren: // A little arrow function is all you need getAllArticles().then(() => getArticleById(2)); Indem Sie den Call von getArticleById mit einer Arrow-Funktion umschliessen, wird .then mit einer Funktion “versorgt”, die es aufrufen kann, wenn getAllArticles() aufgelöst ist. Lösungstipp 2: Benannte Funktionen an .then übergeben Sie müssen jedoch nicht auf anonyme Inline-Funktionen als Argumente für .then zurückgreifen. Stattdessen können Sie einfach einer Variable eine Funktion zuweisen und die Referenz auf diese an .then übergeben. // function definitions from Gotcha #2 let getArticle2 = () => { return getArticleById(2); }; getAllArticles().then(getArticle2); getAllArticles().then(getArticle2); In diesem Beispiel übergeben wir die Referenz lediglich und rufen sie nicht auf. Lösungstipp 3: async/await verwenden Eine weitere Möglichkeit, die Reihenfolge der Ereignisse klarer zu gestalten, besteht darin, die Keywords async/await zu nutzen: async function getSequentially() { const allArticles = await getAllArticles(); // Wait for first call const specificArticle = await getArticleById(2); // Then wait for second // ... use specificArticle } Das legt eindeutig und offensichtlich fest, dass zwei Schritte nacheinander ausgeführt werden – und der Execution-Prozess erst dann weiterläuft, wenn beide abgeschlossen sind. 3. Nicht-funktionale .then-Argumente Das eben behandelte Problem lässt sich noch erweitern: let getAllArticles = () => { return someAjax.get('/articles'); }; let getArticleById = (id) => { return someAjax.get(`/articles/${id}`); }; getAllArticles().then(getArticleById(2)).then((article2) => { // Do something with article2 }); Wir wissen bereits, dass diese Kette nicht wie gewünscht sequenziell ausgeführt wird – haben jetzt aber ein zusätzliches Promise-Problem: Was ist der Wert von article2 im letzten .then? Da wir keine Funktion an das erste Argument von .then übergeben, gibt JavaScript das ursprüngliche Promise mit seinem aufgelösten Wert weiter. Das führt dazu, dass der Value von article2 dem Wert entspricht, den getAllArticles() aufgelöst hat. Wenn Sie es mit einer langen Kette von .then-Methoden zu tun haben und einige Ihrer Handler Werte aus früheren .then-Methoden erhalten, sollten Sie sicherstellen, dass auch tatsächlich Funktionen an .then übergeben werden. Lösungstipp 1: Benannte Funktionen mit formalen Parametern nutzen Eine Möglichkeit, dieses Problem zu händeln, besteht darin, benannte Funktionen zu übergeben, die einen einzelnen, formalen Parameter definieren (also ein Argument entgegennehmen). Das ermöglicht Ihnen, generische Funktionen zu erstellen, die sich sowohl innerhalb als auch außerhalb einer Kette von .then-Methoden nutzen lassen. Angenommen, die Funktion getFirstArticle ruft eine API auf, um den neuesten article in einem Set abzurufen und diesen zu einem article-Objekt aufzulösen – mit Eigenschaften wie ID, Titel und Publikationsdatum. Eine weitere Funktion – getCommentsForArticleId – nimmt eine Artikel-ID entgegen und führt einen API-Call aus, um sämtliche Kommentare zu diesem Artikel abzurufen. Um diese beiden Funktionen miteinander zu verbinden, müssen Sie nun nur noch vom Auflösungswert der ersten Funktion (ein article-Objekt) zum erwarteten Argumentwert der zweiten Funktion (eine Artikel-ID) gelangen. Zu diesem Zweck könnten Sie auf eine anonyme Inline-Funktion zurückgreifen: getFirstArticle().then((article) => { return getCommentsForArticleId(article.id); }); Oder Sie erstellen eine simple Funktion, die einen Artikel entgegennimmt, dessen ID zurückgibt und alles mit .then verknüpft: let extractId = (article) => article.id; getFirstArticle().then(extractId).then(getCommentsForArticleId); Diese Lösung verschleiert den Auflösungswert aller Funktionen, weil diese nicht inline definiert sind. Andererseits entstehen dadurch flexible Funktionen, die sehr wahrscheinlich wiederverwendet werden können. Hier gibt es auch ein “Wiedersehen” mit einem Learning aus unserem ersten behandelten Problem: Zwar gibt extractId kein Promise zurück, dennoch wird .then seinen Rückgabewert in eines verpacken – wodurch Sie .then erneut callen können. Lösungstipp 2: async/await verwenden Auch mit async/await lässt sich an dieser Stelle Abhilfe schaffen – beziehungsweise mehr Übersichtlichkeit herstellen: async function getArticleAndComments() { const article = await getFirstArticle(); const comments = await getCommentsForArticleId(article.id); // Extract ID directly // ... use comments } Hier warten Sie einfach, bis getFirstArticle() beendet ist und verwenden dann den Artikel, um seine ID abzurufen. Das ist möglich, weil sicher ist, dass der Artikel durch die zugrundeliegende Operation aufgelöst wurde. 4. async/await beeinträchtigt Concurrency Falls Sie mehrere asynchrone Prozesse gleichzeitig starten möchten, setzen Sie diese in einen Loop und nutzen await: // (Bad practice below!) async function getMultipleUsersSequentially(userIds) { const users = []; const startTime = Date.now(); for (const id of userIds) { // await pauses the *entire loop* for each fetch const user = await fetchUserDataPromise(id); users.push(user); } const endTime = Date.now(); console.log(`Sequential fetch took ${endTime - startTime}ms`); return users; } // If each fetch takes 1.5s, 3 fetches would take ~4.5s total. In diesem Beispiel wollen wir sämtliche fetchUserDataPromise()-Requests zusammen senden. Allerdings wird im Loop dennoch eine Anfrage nach der anderen ausgeführt. Lösungstipp: Promise.all verwenden Diese Problemstellung lässt sich ganz einfach auflösen – und zwar mit Hilfe von Promise.all: // (Requests happen concurrently) async function getMultipleUsersConcurrently(userIds) { console.log("Starting concurrent fetch..."); const startTime = Date.now(); const promises = userIds.map(id => fetchUserDataPromise(id)); const users = await Promise.all(promises); const endTime = Date.now(); console.log(`Concurrent fetch took ${endTime - startTime}ms`); return users; } // If each fetch takes 1.5s, 3 concurrent fetches would take ~1.5s total (plus a tiny overhead). Dabei bewirkt Promise.all dass sämtliche Promises im Array gleichzeitig gestartet und abgeschlossen werden. In diesem Anwendungsfall sind Promises im Vergleich zu async/await die simplere Lösung (dennoch kommt await zum Einsatz, um darauf zu warten, dass Promise.all abgeschlossen wird). (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox!
Promise-Probleme in JavaScript beheben Promises-Probleme können JavaScript-Projekte ausbremsen.DC Studio | shutterstock.com Das Thema Promises in JavaScript haben wir bereits in einem ausführlichen Tutorial beleuchtet. In diesem Artikel werfen wir einen Blick auf vier häufige Probleme im Zusammenhang mit Promises – und darauf, wie diese sich am besten auflösen lassen. 1. Handler, die Promises zurückgeben Werden Informationen aus einem then– oder catch-Handler zurückgeben, werden diese stets mit einem Promise “gewrappt” (um/eingeschlossen) – sofern es sich nicht bereits um ein Promise handelt. Code wie den folgenden müssen Entwickler also nicht schreiben: firstAjaxCall.then(() => { return new Promise((resolve, reject) => { nextAjaxCall().then(() => resolve()); }); }); Weil nextAjaxCall ebenfalls ein Promise zurückgibt, können Sie sich stattdessen beschränken auf: firstAjaxCall.then(() => { return nextAjaxCall(); }); Wird darüber hinaus ein einfacher Wert (kein Promise) zurückgegeben, gibt der Handler ein Promise zurück, das auf diesen Wert (Value) aufgelöst wird. Sie können also weiterhin .then nutzen, um die Ergebnisse zu callen: firstAjaxCall.then((response) => { return response.importantField }).then((resolvedValue) => { // resolvedValue is the value of response.importantField returned above console.log(resolvedValue); }); Das ist praktisch – wirft aber die Frage auf, wie zu verfahren ist, wenn Sie den State eines eingehenden Werts (Values) nicht kennen. Lösungstipp: Promise.resolve() nutzen Wenn Sie sich unsicher sind, ob Ihr eingehender Wert bereits ein Promise ist oder nicht, können Sie einfach auf die statische Promise.resolve()-Methode zurückgreifen. Betreffende Variablen können als Argument an Promise.resolve übergeben werden. Handelt es sich um ein Promise, wird dieses von der Methode zurückgegeben. Handelt es sich bei der Variable hingegen um einen Value, gibt die Methode ein Promise zurück, das auf den Wert aufgelöst wurde: let processInput = (maybePromise) => { let definitelyPromise = Promise.resolve(maybePromise); definitelyPromise.then(doSomeWork); }; 2. .then nimmt stets eine Funktion Sehr wahrscheinlich haben schon einmal Promise-Code gesehen (oder geschrieben). Dieser sieht in etwa so aus: let getAllArticles = () => { return someAjax.get('/articles'); }; let getArticleById = (id) => { return someAjax.get(`/articles/${id}`); }; getAllArticles().then(getArticleById(2)); Dieser Code soll den Zweck erfüllen, zunächst alle Artikel abzurufen – und anschließend den mit der ID 2. Auch wenn die Intention war, diese Aktionen sequenziell auszuführen, werden die beiden Promises im Wesentlichen zur selben Zeit gestartet. Die Reihenfolge ist also dem Zufall überlassen. Das Problem: Bei diesem Code haben wir eine JavaScript-Grundregel außer Acht gelassen: Argumente für Funktionen werden immer ausgewertet, bevor sie an die Funktion übergeben werden. In unserem Fall geht .then aber keine Funktion zu, sondern der Rückgabewert von getArticleById. Das liegt daran, dass getArticleById direkt mit dem Klammeroperator aufgerufen wird. Um das zu beheben, gibt es mehrere Möglichkeiten. Lösungstipp 1: Arrow-Funktion als Wrapper nutzen Um sicherzustellen, dass beide Funktionen nacheinander ausgeführt werden, könnten Sie folgendermaßen verfahren: // A little arrow function is all you need getAllArticles().then(() => getArticleById(2)); Indem Sie den Call von getArticleById mit einer Arrow-Funktion umschliessen, wird .then mit einer Funktion “versorgt”, die es aufrufen kann, wenn getAllArticles() aufgelöst ist. Lösungstipp 2: Benannte Funktionen an .then übergeben Sie müssen jedoch nicht auf anonyme Inline-Funktionen als Argumente für .then zurückgreifen. Stattdessen können Sie einfach einer Variable eine Funktion zuweisen und die Referenz auf diese an .then übergeben. // function definitions from Gotcha #2 let getArticle2 = () => { return getArticleById(2); }; getAllArticles().then(getArticle2); getAllArticles().then(getArticle2); In diesem Beispiel übergeben wir die Referenz lediglich und rufen sie nicht auf. Lösungstipp 3: async/await verwenden Eine weitere Möglichkeit, die Reihenfolge der Ereignisse klarer zu gestalten, besteht darin, die Keywords async/await zu nutzen: async function getSequentially() { const allArticles = await getAllArticles(); // Wait for first call const specificArticle = await getArticleById(2); // Then wait for second // ... use specificArticle } Das legt eindeutig und offensichtlich fest, dass zwei Schritte nacheinander ausgeführt werden – und der Execution-Prozess erst dann weiterläuft, wenn beide abgeschlossen sind. 3. Nicht-funktionale .then-Argumente Das eben behandelte Problem lässt sich noch erweitern: let getAllArticles = () => { return someAjax.get('/articles'); }; let getArticleById = (id) => { return someAjax.get(`/articles/${id}`); }; getAllArticles().then(getArticleById(2)).then((article2) => { // Do something with article2 }); Wir wissen bereits, dass diese Kette nicht wie gewünscht sequenziell ausgeführt wird – haben jetzt aber ein zusätzliches Promise-Problem: Was ist der Wert von article2 im letzten .then? Da wir keine Funktion an das erste Argument von .then übergeben, gibt JavaScript das ursprüngliche Promise mit seinem aufgelösten Wert weiter. Das führt dazu, dass der Value von article2 dem Wert entspricht, den getAllArticles() aufgelöst hat. Wenn Sie es mit einer langen Kette von .then-Methoden zu tun haben und einige Ihrer Handler Werte aus früheren .then-Methoden erhalten, sollten Sie sicherstellen, dass auch tatsächlich Funktionen an .then übergeben werden. Lösungstipp 1: Benannte Funktionen mit formalen Parametern nutzen Eine Möglichkeit, dieses Problem zu händeln, besteht darin, benannte Funktionen zu übergeben, die einen einzelnen, formalen Parameter definieren (also ein Argument entgegennehmen). Das ermöglicht Ihnen, generische Funktionen zu erstellen, die sich sowohl innerhalb als auch außerhalb einer Kette von .then-Methoden nutzen lassen. Angenommen, die Funktion getFirstArticle ruft eine API auf, um den neuesten article in einem Set abzurufen und diesen zu einem article-Objekt aufzulösen – mit Eigenschaften wie ID, Titel und Publikationsdatum. Eine weitere Funktion – getCommentsForArticleId – nimmt eine Artikel-ID entgegen und führt einen API-Call aus, um sämtliche Kommentare zu diesem Artikel abzurufen. Um diese beiden Funktionen miteinander zu verbinden, müssen Sie nun nur noch vom Auflösungswert der ersten Funktion (ein article-Objekt) zum erwarteten Argumentwert der zweiten Funktion (eine Artikel-ID) gelangen. Zu diesem Zweck könnten Sie auf eine anonyme Inline-Funktion zurückgreifen: getFirstArticle().then((article) => { return getCommentsForArticleId(article.id); }); Oder Sie erstellen eine simple Funktion, die einen Artikel entgegennimmt, dessen ID zurückgibt und alles mit .then verknüpft: let extractId = (article) => article.id; getFirstArticle().then(extractId).then(getCommentsForArticleId); Diese Lösung verschleiert den Auflösungswert aller Funktionen, weil diese nicht inline definiert sind. Andererseits entstehen dadurch flexible Funktionen, die sehr wahrscheinlich wiederverwendet werden können. Hier gibt es auch ein “Wiedersehen” mit einem Learning aus unserem ersten behandelten Problem: Zwar gibt extractId kein Promise zurück, dennoch wird .then seinen Rückgabewert in eines verpacken – wodurch Sie .then erneut callen können. Lösungstipp 2: async/await verwenden Auch mit async/await lässt sich an dieser Stelle Abhilfe schaffen – beziehungsweise mehr Übersichtlichkeit herstellen: async function getArticleAndComments() { const article = await getFirstArticle(); const comments = await getCommentsForArticleId(article.id); // Extract ID directly // ... use comments } Hier warten Sie einfach, bis getFirstArticle() beendet ist und verwenden dann den Artikel, um seine ID abzurufen. Das ist möglich, weil sicher ist, dass der Artikel durch die zugrundeliegende Operation aufgelöst wurde. 4. async/await beeinträchtigt Concurrency Falls Sie mehrere asynchrone Prozesse gleichzeitig starten möchten, setzen Sie diese in einen Loop und nutzen await: // (Bad practice below!) async function getMultipleUsersSequentially(userIds) { const users = []; const startTime = Date.now(); for (const id of userIds) { // await pauses the *entire loop* for each fetch const user = await fetchUserDataPromise(id); users.push(user); } const endTime = Date.now(); console.log(`Sequential fetch took ${endTime - startTime}ms`); return users; } // If each fetch takes 1.5s, 3 fetches would take ~4.5s total. In diesem Beispiel wollen wir sämtliche fetchUserDataPromise()-Requests zusammen senden. Allerdings wird im Loop dennoch eine Anfrage nach der anderen ausgeführt. Lösungstipp: Promise.all verwenden Diese Problemstellung lässt sich ganz einfach auflösen – und zwar mit Hilfe von Promise.all: // (Requests happen concurrently) async function getMultipleUsersConcurrently(userIds) { console.log("Starting concurrent fetch..."); const startTime = Date.now(); const promises = userIds.map(id => fetchUserDataPromise(id)); const users = await Promise.all(promises); const endTime = Date.now(); console.log(`Concurrent fetch took ${endTime - startTime}ms`); return users; } // If each fetch takes 1.5s, 3 concurrent fetches would take ~1.5s total (plus a tiny overhead). Dabei bewirkt Promise.all dass sämtliche Promises im Array gleichzeitig gestartet und abgeschlossen werden. In diesem Anwendungsfall sind Promises im Vergleich zu async/await die simplere Lösung (dennoch kommt await zum Einsatz, um darauf zu warten, dass Promise.all abgeschlossen wird). (fm) Sie wollen weitere interessante Beiträge zu diversen Themen aus der IT-Welt lesen? Unsere kostenlosen Newsletter liefern Ihnen alles, was IT-Profis wissen sollten – direkt in Ihre Inbox!