Warum Ihr async/await in forEach schweigt? Wir klären auf den ersten Blick
\n\nHaben Sie jemals so etwas wie diesen Code geschrieben, in der Hoffnung, dass er asynchrone Operationen der Reihe nach ausführt?
\n\nconst users = ['alice', 'bob', 'charlie'];\n\nusers.forEach(async (user) => {\n const data = await fetch(`https://api.example.com/users/${user}`);\n console.log(await data.json());\n});\n\nconsole.log('Fertig!');\n\nUnd bekamen "Fertig!" bevor die Daten geladen wurden? Kommt Ihnen bekannt vor? Sie sind nicht allein. Dies ist eine der häufigsten Fragen auf Stack Overflow mit tausenden von Aufrufen. Das Problem ist, dass forEach eine synchrone Methode ist. Sie ruft einfach die Callbacks auf, aber wartet nicht auf deren Abschluss. Ihre async-Funktionen starten, aber forEach eilt bereits weiter, wie ein Sprinter, dem das Ziel der anderen egal ist.
In diesem Artikel zeigen wir nicht nur, wie man das behebt. Wir analysieren warum das passiert und geben Ihnen drei funktionierende Lösungen mit Beispielen. Sie erfahren, wann Sie Promise.all und wann den guten alten for...of verwenden. Los geht's!
Anatomie des Problems: forEach und Promises
\n\nSchauen wir unter die Haube. Die Methode forEach in JavaScript ist so aufgebaut:
Array.prototype.forEach = function(callback) {\n for (let i = 0; i < this.length; i++) {\n callback(this[i], i, this);\n }\n};\n\nSehen Sie? Sie ruft einfach callback für jedes Element auf, gibt aber nichts zurück. Selbst wenn der Callback ein Promise zurückgibt, ignoriert forEach es. Es wartet nicht, sammelt keine Ergebnisse – es startet einfach und vergisst.
Wenn Sie async (user) => { ... } schreiben, gibt Ihre Funktion ein Promise zurück. Aber forEach weiß nicht, was damit zu tun ist. Infolgedessen werden alle Anfragen parallel gestartet (wenn es sich um fetch handelt), aber Sie können nicht auf deren Abschluss warten. Der Code nach forEach wird sofort ausgeführt.
Praktisches Beispiel: Wie es in der Praxis aussieht
\n\nStellen Sie sich vor, Sie verarbeiten ein Array von Produkt-IDs und müssen für jede den Preis aus der Datenbank abrufen:
\n\nconst productIds = [101, 102, 103];\n\nasync function getPrice(id) {\n // Simulation einer Datenbankabfrage\n return new Promise(resolve => {\n setTimeout(() => {\n resolve(`Preis für ${id}: $${Math.floor(Math.random() * 100)}`);\n }, 1000);\n });\n}\n\nproductIds.forEach(async (id) => {\n const price = await getPrice(id);\n console.log(price);\n});\n\nconsole.log('Alle Preise geladen!'); // Wird sofort ausgeführt, nicht nach 3 Sekunden\n\nErgebnis in der Konsole:
\nAlle Preise geladen!\nPreis für 101: $42\nPreis für 102: $78\nPreis für 103: $15\n\nGeben Sie zu, das ist nicht das, was wir erwartet haben. Die gute Nachricht: Es gibt einfache und elegante Lösungen.
\n\nLösung Nr. 1: for...of — sequentielle Ausführung
\n\nDer einfachste Weg, async/await in einer Schleife zum Laufen zu bringen, ist die Verwendung von for...of. Im Gegensatz zu forEach arbeitet for...of nativ mit async/await, da Sie den Ausführungsfluss innerhalb des Blocks steuern.
const productIds = [101, 102, 103];\n\nasync function getPrice(id) {\n return new Promise(resolve => {\n setTimeout(() => {\n resolve(`Preis für ${id}: $${Math.floor(Math.random() * 100)}`);\n }, 1000);\n });\n}\n\nasync function loadAllPrices() {\n for (const id of productIds) {\n const price = await getPrice(id);\n console.log(price);\n }\n console.log('Alle Preise geladen!');\n}\n\nloadAllPrices();\n\nJetzt wird das Ergebnis so aussehen:
\nPreis für 101: $42\n(nach 1 Sekunde)\nPreis für 102: $78\n(nach 1 Sekunde)\nPreis für 103: $15\n(nach 1 Sekunde)\nAlle Preise geladen!\n\nVorteile: Einfachheit, Lesbarkeit, vorhersagbare Reihenfolge.
\nNachteile: Langsam – jede Anfrage wartet auf den Abschluss der v