Lösen Sie das Problem, dass await in forEach nicht funktioniert

Lösen Sie das Problem, dass await in forEach nicht funktioniert

1. Einleitung

Vor ein paar Tagen bin ich bei der Verwendung zur Durchquerung in einem Projekt auf eine Falle gestoßen und habe einen Tag gebraucht, um sie zu beheben. Merken Sie es sich hier einfach.

2. Problem

Lassen Sie mich zunächst ein sehr einfaches Thema vorstellen: Drucken Sie ein Array alle 1 Sekunde aus. Hier füge ich den Code ein, den ich im Projekt begonnen habe. (Das hat natürlich nichts mit dem Geschäft zu tun.)

const _ = erfordern('lodash');
const echo = async (i) => {
  setzeTimeout(() => {
    Konsole.log('i===>', i);
  }, 5000);
}
sei arrs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const task = async () => {
  _.forEach(arrs, async (i) => {
    warte auf Echo(i);
  })
}
const run = async () => {
  Konsole.log('run-start====>date:', neues Date().toLocaleDateString())
  warte auf Aufgabe();
  Konsole.log('run-end====>date:', neues Date().toLocaleDateString())
}
(asynchron () => {
  console.log('starten...')
  warte auf Ausführung();
  console.log('Ende ...')
})()
// Start...
// Lauf-Start====>Datum: 25.08.2018
// Laufende====>Datum: 25.08.2018
// Ende...
// ich ===> 1
// ich ===> 2
// ich ===> 3
// ich ===> 4
// ich ===> 5
// ich ===> 6
// ich ===> 7
// ich ===> 8
// ich ===> 9

Der obige Code und die Ausgabe wurden angegeben. Es ist seltsam, dass das Warten hier keine Wirkung hat. Zuerst gab es ein Problem mit meinem Geschäftscode, weil ich das Geschäft hinzugefügt habe, und dann habe ich den Code extrahiert, aber es hat immer noch nicht funktioniert. Zu diesem Zeitpunkt habe ich wirklich an dem Warten gezweifelt.

Abschließend wird die Antwort auf die Frage gegeben:

lodashs forEach und [].forEach unterstützen await nicht. Wenn Sie await während des Durchlaufens ausführen müssen, können Sie for-of verwenden.

Hier ist der richtige Code:

const _ = erfordern('lodash');
const echo = async (i) => {
  returniere neues Promise((lösen,ablehnen)=>{
    setzeTimeout(() => {
      console.log('i===>', i, neues Date().toLocaleTimeString());
      Entschlossenheit (i);
    }, 2000);
  })
}
sei arrs = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const task = async () => {
  // _.forEach(arrs, async (i) => {
  // warte auf echo(ji);
  // })
  // arrs.forEach(async (i)=> {
  // warte auf echo(i);
  // });
  für (const i von arrs) {
    warte auf Echo(i);
  }
}
const run = async () => {
  Konsole.log('run-start====>date:', neues Date().toLocaleDateString())
  warte auf Aufgabe();
  Konsole.log('run-end====>date:', neues Date().toLocaleDateString())
}
(asynchron () => {
  console.log('starten...')
  warte auf Ausführung();
  console.log('Ende ...')
})()
// Ausgabe starten...
Lauf-Start====>Datum: 26.8.2018
ich===> 1 20:51:29
ich===> 2 20:51:31
ich===> 3 20:51:33
ich===> 4 20:51:35
ich===> 5 20:51:37
ich===> 6 20:51:39
ich===> 7 20:51:42
ich===> 8 20:51:44
ich===> 9 20:51:46
ich===> 10 20:51:48
Laufende====>Datum: 26.8.2018
Ende...

Abschluss

Beim Lösen eines Problems kann man manchmal das Ausschlussverfahren anwenden. In diesem Beispiel wissen wir beispielsweise, dass der Wartemechanismus einwandfrei sein muss. Wenn es ein Problem gibt, bin ich definitiv nicht an der Reihe, es zu testen. Das verbleibende Problem kann also nur die Ursache für die For-Durchquerung sein.

Da ich es am Anfang mit lodash implementiert habe, könnte ich mich fragen, ob lodashs forEach keine (oder redundante) Await-Verarbeitung durchgeführt hat. Zu diesem Zeitpunkt könnte ich es auf eine andere Weise versuchen. Im Allgemeinen ist es eine Frage der Erfahrung.

Ergänzung: Probleme bei der Verwendung von async/await in forEach

1. Problembeschreibung

Vor einigen Tagen ist bei mir im Projekt ein asynchrones JavaScript-Problem aufgetreten:

Es gibt einen Datensatz, von dem jeder asynchron verarbeitet werden muss, und es ist zu hoffen, dass die Verarbeitung synchron erfolgt.

Die Codebeschreibung lautet wie folgt:

// Daten generieren const getNumbers = () => {
 returniere Promise.resolve([1, 2, 3])
}
// Asynchrone Verarbeitung const doMulti = num => {
 returniere neues Promise((lösen, ablehnen) => {
  setzeTimeout(() => {
   wenn (Zahl) {
    auflösen(Zahl * Zahl)
   } anders {
    ablehnen(neuer Fehler('Nummer nicht angegeben'))
   }
  }, 2000)
 })
}
// Hauptfunktion const main = async () => {
 Konsole.log('Start');
 Konstantennummern = [1, 2, 3];
 nums.forEach(async (x) => {
  const res = warte auf doMulti(x);
  konsole.log(res);
 });
 konsole.log('Ende');
};
// main() ausführen;

In diesem Beispiel iteriert forEach über jede Zahl und führt die doMulti-Operation aus. Das Ergebnis der Codeausführung ist: Zuerst werden Start und Ende sofort ausgedruckt. Nach 2 Sekunden werden 1, 4 und 9 gleichzeitig ausgegeben.

Dieses Ergebnis unterscheidet sich etwas von dem, was wir erwartet haben. Wir hoffen, alle 2 Sekunden eine asynchrone Verarbeitung durchzuführen und nacheinander 1, 4 und 9 auszugeben. Der aktuelle Code sollte also parallel ausgeführt werden, wir erwarten jedoch eine seriell ausgeführte Ausführung.

Versuchen wir, die forEach-Schleife durch eine for-Schleife zu ersetzen:

const main = async () => {
 Konsole.log('Start');
 const nums = warte auf getNumbers();
 für (const x von Nums) {
  const res = warte auf doMulti(x);
  konsole.log(res);
 }
 konsole.log('Ende');
};

Das Ausführungsergebnis ist genau wie erwartet: Die Ausgabe lautet: Start, 1, 4, 9, Ende.

2. Problemanalyse

Die Ideen sind dieselben, aber die verwendeten Durchquerungsmethoden sind unterschiedlich. Warum ist das so? Ich habe auf MDN nach dem Polyfill für forEach gesucht. Referenz MDN-Array.prototype.forEach():

// Produktionsschritte von ECMA-262, Ausgabe 5, 15.4.4.18
// Referenz: http://es5.github.io/#x15.4.4.18
wenn (!Array.prototype.forEach) {
 Array.prototype.forEach = Funktion(Rückruf, diesesArg) {
  var T, k;
  wenn (dies == null) {
   throw new TypeError('dies ist null oder nicht definiert');
  }
  // 1. O sei das Ergebnis des Aufrufs von toObject() und übergibt die
  // |diesen| Wert als Argument.
  var O = Objekt(dieses);
  // 2. Lassen Sie lenValue das Ergebnis des Aufrufs von Get() internal sein.
  // Methode von O mit dem Argument „Länge“.
  // 3. Lass len toUint32(lenValue) sein.
  var len = O.Länge >>> 0;
  // 4. Wenn isCallable(callback) false ist, werfen Sie eine TypeError-Ausnahme. 
  // Siehe: http://es5.github.com/#x9.11
  wenn (Typ des Rückrufs !== "Funktion") {
   throw new TypeError(callback + ' ist keine Funktion');
  }
  // 5. Wenn thisArg angegeben wurde, lass T thisArg sein; sonst lass
  // T ist nicht definiert.
  if (Argumente.Länge > 1) {
   T = diesesArg;
  }
  // 6. Sei k 0
  k = 0;
  // 7. Wiederholen, solange k < len
  während (k < Länge) {
   var kWert;
   // a. Lassen Sie Pk ToString(k) sein.
   // Dies ist implizit für LHS-Operanden des in-Operators
   // b. Lassen Sie kPresent das Ergebnis des Aufrufs von HasProperty sein
   // interne Methode von O mit Argument Pk.
   // Dieser Schritt kann mit c kombiniert werden
   // c. Wenn kPresent wahr ist, dann
   wenn (k in O) {
    // i. Lassen Sie kValue das Ergebnis des Aufrufs von Get internal sein.
    // Methode von O mit Argument Pk.
    kWert = O[k];
    // ii. Rufen Sie die interne Callback-Methode mit T als
    // dieser Wert und die Argumentliste, die kValue, k und O enthält.
    Rückruf.call(T, kValue, k, O);
   }
   // d. Erhöhe k um 1.
   k++;
  }
  // 8. Rückgabe undefiniert
 };
}

Aus Schritt 7 im obigen Polyfill können wir die folgenden Schritte einfach verstehen:

Array.prototype.forEach = Funktion (Rückruf) {
 // dies stellt unser Array dar
 für (let index = 0; index < this.length; index++) {
  // Wir rufen den Callback für jeden Eintrag auf
  Rückruf(dies[Index], Index, dies);
 };
};

Dies entspricht einer For-Schleife, die diese asynchrone Funktion ausführt. Sie wird also parallel ausgeführt, wodurch alle Ausgabeergebnisse gleichzeitig ausgegeben werden: 1, 4, 9.

const main = async () => {
 Konsole.log('Start');
 const nums = warte auf getNumbers();
 // nums.forEach(async (x) => {
 // const res = warte auf doMulti(x);
 // konsole.log(res);
 // });
 für (let index = 0; index < nums.length; index++) {
  (async x => {
   const res = warte auf doMulti(x)
   Konsole.log(res)
  })(Zahlen[Index])
 }
 konsole.log('Ende');
};

3. Lösung

Jetzt haben wir das Problem klar analysiert. In der vorherigen Lösung haben wir die for-of-Schleife anstelle von forEach verwendet. Tatsächlich können wir forEach auch ändern:

const asyncForEach = async (Array, Rückruf) => {
 für (let index = 0; index < array.length; index++) {
  warte auf Rückruf (Array[Index], Index, Array);
 }
}
const main = async () => {
 Konsole.log('Start');
 const nums = warte auf getNumbers();
 warte auf asyncForEach(nums, async x => {
  const res = warte auf doMulti(x)
  Konsole.log(res)
 })
 konsole.log('Ende');
};
hauptsächlich();

IV. Eslint-Probleme

Zu diesem Zeitpunkt meldete Eslint einen weiteren Fehler: „no-await-in-loop“. Zu diesem Punkt wird es auch im offiziellen Eslint-Dokument https://eslint.org/docs/rules/no-await-in-loop erklärt.

Gutes Schreiben:

asynchrone Funktion foo(Dinge) {
 const Ergebnisse = [];
 für (const Ding von Dingen) {
  // Gut: Alle asynchronen Operationen werden sofort gestartet.
  Ergebnisse.push(Bar(Ding));
 }
 // Da nun alle asynchronen Vorgänge ausgeführt werden, warten wir hier, bis sie alle abgeschlossen sind.
 returniere baz(warte auf Promise.all(Ergebnisse));
}

Schlechtes Schreiben:

asynchrone Funktion foo(Dinge) {
 const Ergebnisse = [];
 für (const Ding von Dingen) {
  // Schlecht: jede Schleifeniteration wird verzögert, bis der gesamte asynchrone Vorgang abgeschlossen ist
  Ergebnisse.push(warte auf Balken(Ding));
 }
 gib baz(Ergebnisse) zurück;
}

Tatsächlich gibt es zwischen den beiden oben genannten Schreibweisen keinen Unterschied im Guten oder Schlechten. Die Ergebnisse dieser beiden Schreibweisen sind völlig unterschiedlich. Die von Eslint empfohlene „gute Schreibmethode“ hat bei der Ausführung asynchroner Vorgänge keine Reihenfolge, während die „schlechte Schreibmethode“ eine Reihenfolge hat. Die zu verwendende spezifische Schreibmethode sollte basierend auf den Geschäftsanforderungen bestimmt werden.

Daher erwähnt Eslint im Abschnitt „Wann sollte es nicht verwendet werden“ des Dokuments auch, dass wir diese Regel deaktivieren können, wenn eine sequentielle Ausführung erforderlich ist:

In vielen Fällen sind die Iterationen einer Schleife nicht wirklich unabhängig voneinander. Beispielsweise kann die Ausgabe einer Iteration als Eingabe für eine andere verwendet werden. Oder Schleifen können verwendet werden, um asynchrone Vorgänge zu wiederholen, die nicht erfolgreich waren. Oder Schleifen können verwendet werden, um zu verhindern, dass Ihr Code eine übermäßige Anzahl von Anfragen parallel sendet. In solchen Fällen ist es sinnvoll, „await“ innerhalb einer Schleife zu verwenden, und es wird empfohlen, die Regel über einen standardmäßigen ESLint-Deaktivierungskommentar zu deaktivieren.

Das Obige ist meine persönliche Erfahrung. Ich hoffe, es kann Ihnen als Referenz dienen. Ich hoffe auch, dass Sie 123WORDPRESS.COM unterstützen werden. Sollten dennoch Fehler oder unvollständige Überlegungen vorliegen, freue ich mich über eine Korrektur.

Das könnte Sie auch interessieren:
  • Lösung für das Problem der ungültigen Rückgabe in JavaScript forEach
  • Async/await ermöglicht die synchrone Ausführung asynchroner Operationen
  • Lösen Sie das Problem des fehlgeschlagenen Mybatis-Batch-Updates (Update foreach)
  • Lösen Sie das Problem von Mybatis mithilfe der Foreach-Batch-Insert-Ausnahme

<<:  Lösung für das Problem, dass der MySQL-Dienst gestartet wird, aber keine Verbindung hergestellt wird

>>:  So stellen Sie DoNetCore mit Nginx in der Alibaba Cloud bereit

Artikel empfehlen

Zusammenfassung zum horizontal scrollenden Website-Design

Horizontales Scrollen ist nicht in allen Situation...

So mounten Sie eine Festplatte in Linux

Wenn Sie eine virtuelle Maschine verwenden, stell...

Tutorial zur Installation und Konfiguration von MySQL 5.7.16 ZIP-Paketen

In diesem Artikel finden Sie das Installations- u...

Vue.js implementiert Kalenderfunktion

In diesem Artikelbeispiel wird der spezifische Co...

Erläuterung von JavaScript-Mikrotasks und Makrotasks

Vorwort: js ist eine Single-Thread-Sprache, daher...

Erfahren Sie, wie Sie mit Webpack TypeScript-Code verpacken und kompilieren

TypeScript-Bündelung Webpack-Integration Normaler...

Beispiel zum Ändern von Stilen über CSS-Variablen

Frage Wie ändere ich den CSS-Pseudoklassenstil mi...

Warum TypeScripts Enum problematisch ist

Inhaltsverzeichnis Was ist passiert? Verwendung S...

So ermitteln Sie die Größe eines Linux-Systemverzeichnisses mit dem Befehl du

Jeder, der das Linux-System verwendet hat, sollte...