Flucht aus der Hölle asynchron / warte

Ursprünglicher Autor: Aditya Agarwal
  • Übersetzung
In jüngerer Zeit schien das async / await-Konstrukt in JavaScript eine großartige Möglichkeit zu sein, die Hölle der Rückrufe loszuwerden. Die unvorsichtige Verwendung von async / await hat jedoch zur Entstehung einer neuen Hölle geführt.


Der Autor des Materials, dessen Übersetzung wir heute veröffentlichen, spricht darüber, was zum Teufel asynchron ist / erwartet und wie man dem entkommt.

Was ist die Hölle asynchron / erwarten


Wenn Programmierer mit den asynchronen Funktionen von JavaScript arbeiten, verwenden sie häufig Konstrukte, die aus vielen Funktionsaufrufen bestehen, denen jeweils ein Schlüsselwort vorangestellt ist await. Da in solchen Fällen die Ausdrücke c häufig awaitunabhängig voneinander sind, führt ein solcher Code zu Leistungsproblemen. Der Punkt hier ist, dass das System warten muss, bis die vorherige Funktion abgeschlossen ist, bevor es die nächste Funktion ausführen kann. Dies ist die Hölle asynchron / warten.

Beispiel: Bestellung von Pizza und Getränken


Stellen Sie sich vor, wir müssen ein Skript schreiben, das Bestellungen für Pizza und Getränke aufgibt. Dieses Skript könnte folgendermaßen aussehen:

(async () => {
  const pizzaData = await getPizzaData()    // асинхронный вызов
  const drinkData = await getDrinkData()    // асинхронный вызов
  const chosenPizza = choosePizza()    // синхронный вызов
  const chosenDrink = chooseDrink()    // синхронный вызов
  await addPizzaToCart(chosenPizza)    // асинхронный вызов
  await addDrinkToCart(chosenDrink)    // асинхронный вызов
  orderItems()    // асинхронный вызов
})()

Auf den ersten Blick sieht das Skript ganz normal aus, außerdem funktioniert es wie erwartet. Bei sorgfältiger Prüfung dieses Codes stellt sich jedoch heraus, dass seine Implementierung lahm ist, da die Merkmale der asynchronen Codeausführung nicht berücksichtigt werden. Nachdem wir uns mit dem befasst haben, was hier falsch ist, können wir das Problem dieses Skripts lösen.

Der Code wird in einen asynchronen, sofort aufgerufenen Funktionsausdruck ( IIFE ) eingeschlossen. Bitte beachten Sie, dass alle Aufgaben in der genauen Reihenfolge ausgeführt werden, in der sie im Code aufgeführt sind. Um mit der nächsten Aufgabe fortzufahren, müssen Sie warten, bis die vorherige abgeschlossen ist. Das passiert nämlich hier:

  1. Eine Liste der Pizzatypen abrufen.
  2. Eine Getränkekarte bekommen.
  3. Pizzaauswahl aus der Liste.
  4. Wählen Sie ein Getränk aus der Liste.
  5. Die ausgewählte Pizza in den Warenkorb legen.
  6. Das ausgewählte Getränk in den Warenkorb legen.
  7. Kasse.

Die Betonung oben ist, dass Operationen in einem Skript streng sequentiell ausgeführt werden. Die parallele Ausführung von Code wird nicht ausgenutzt. Lassen Sie uns über Folgendes nachdenken: Warum erwarten wir eine Liste mit Pizzasorten, um mit dem Herunterladen der Getränkeliste zu beginnen? Es wäre notwendig, diese Aufgaben gleichzeitig auszuführen. Um jedoch eine Pizza aus der Liste auswählen zu können, müssen Sie zunächst warten, bis die Liste der Pizzatypen geladen ist. Gleiches gilt für die Auswahl eines Getränks.

Infolgedessen kann der Schluss gezogen werden, dass Aufgaben im Zusammenhang mit Pizza und Aufgaben im Zusammenhang mit Getränken parallel ausgeführt werden können, aber einzelne Vorgänge, die sich nur auf Pizza (oder nur auf Getränke) beziehen, müssen nacheinander ausgeführt werden.

Beispiel: Eine Bestellung basierend auf dem Inhalt des Warenkorbs aufgeben


Hier ist ein Beispiel für einen Code, in dem Daten zum Inhalt des Warenkorbs heruntergeladen und eine Anfrage zum Bilden einer Bestellung gesendet werden:

async function orderItems() {
  const items = await getCartItems()    // асинхронный вызов
  const noOfItems = items.length
  for(var i = 0; i < noOfItems; i++) {
    await sendRequest(items[i])    // асинхронный вызов
  }
}

In diesem Fall muss die Schleife forauf den Abschluss jedes Funktionsaufrufs warten sendRequest(), um mit der nächsten Iteration fortzufahren. Diese Erwartung brauchen wir aber nicht wirklich. Wir möchten alle Anfragen so schnell wie möglich bearbeiten und warten, bis sie abgeschlossen sind.
Hoffentlich verstehen Sie jetzt die Essenz von Hell async / await und wie sehr sich dies auf die Anwendungsleistung auswirken kann. Denken Sie nun an die Frage im Titel des nächsten Abschnitts.

Was ist, wenn Sie vergessen, das Schlüsselwort await zu verwenden?


Wenn Sie vergessen, das Schlüsselwort awaitbeim Aufrufen einer asynchronen Funktion zu verwenden, wird die Funktion gerade ausgeführt. Eine solche Funktion gibt ein Versprechen zurück, das später verwendet werden kann.

(async () => {
  const value = doSomeAsyncTask()
  console.log(value) // неразрешённый промис
})()

Eine weitere Konsequenz des Aufrufs einer asynchronen Funktion ohne awaitist, dass der Compiler nicht weiß, dass der Programmierer warten möchte, bis die Funktion abgeschlossen ist. Infolgedessen beendet der Compiler das Programm, ohne die asynchrone Task abzuschließen. Daher sollten Sie das Schlüsselwort nicht vergessen, awaitwo es benötigt wird.

Versprechen haben eine interessante Eigenschaft: In einer Codezeile kann ein Versprechen abgerufen werden, in einer anderen - warten Sie auf dessen Lösung. Diese Tatsache ist der Schlüssel, um der Hölle asynchron zu entkommen.

(async () => {
  const promise = doSomeAsyncTask()
  const value = await promise
  console.log(value) // реальное значение
})()

Wie Sie sehen, gibt der Anruf doSomeAsyncTask()ein Versprechen zurück. Zu diesem Zeitpunkt beginnt die Ausführung dieser Funktion. Um das Ergebnis der Einlösung eines Versprechens zu erhalten, verwenden wir ein Schlüsselwort awaitund teilen dem System damit mit, dass die nächste Codezeile nicht sofort ausgeführt werden soll. Stattdessen müssen Sie warten, bis das Versprechen erfüllt ist, und erst dann mit der nächsten Zeile fortfahren.

Wie komme ich asynchron aus der Hölle?


Um aus der Hölle herauszukommen, können Sie den folgenden Aktionsplan verwenden.

▍1. Suchen Sie nach Ausdrücken, die von der Ausführung anderer Ausdrücke abhängen


Das erste Beispiel zeigte ein Skript zur Auswahl von Pizza und einem Getränk. Bevor wir die Möglichkeit haben, Pizza aus der Liste auszuwählen, müssen wir eine Liste mit Pizzatypen herunterladen. Und bevor Sie Pizza in den Warenkorb legen, müssen Sie sie auswählen. Infolgedessen können wir sagen, dass diese drei Schritte voneinander abhängen. Sie können nicht mit dem nächsten Schritt fortfahren, ohne den vorherigen abgeschlossen zu haben.

Wenn Sie nun breiter denken und über Getränke nachdenken, stellt sich heraus, dass der Prozess der Auswahl von Pizza nicht vom Prozess der Auswahl eines Getränks abhängt, sodass diese beiden Aufgaben parallel ausgeführt werden können. Computer können sehr gut mit parallelen Aufgaben umgehen.

Als Ergebnis wurde uns klar, welche bestimmten Ausdrücke voneinander abhängen und welche nicht.

▍2. Gruppenabhängige Ausdrücke in separaten asynchronen Funktionen


Wie wir bereits herausgefunden haben, besteht der Pizzaauswahlprozess aus mehreren Schritten: Laden einer Liste von Pizzasorten, Auswählen einer bestimmten Pizza und Hinzufügen dieser in den Warenkorb. Es sind diese Aktionen, die zu einer separaten asynchronen Funktion zusammengefasst werden müssen. Nicht zu vergessen, dass eine ähnliche Abfolge von Aktionen auch für Getränke charakteristisch ist, kommen wir zu zwei asynchronen Funktionen, die als selectPizza()und bezeichnet werden können selectDrink().

▍3. Empfangene asynchrone Funktionen parallel ausführen


Jetzt verwenden wir die Funktionen der JavaScript-Ereignisschleife, um die parallele, nicht blockierende Ausführung der empfangenen asynchronen Funktionen zu organisieren. Hierbei werden zwei gängige Muster angewendet - die frühe Rückgabe von Versprechungen und die Methode Promise.all().

Fehlerbehandlung


Wir werden die drei oben beschriebenen Schritte in die Praxis umsetzen, um die asynchrone / erwartete Hölle loszuwerden. Korrigieren Sie die obigen Beispiele. So sieht der erste jetzt aus.

async function selectPizza() {
  const pizzaData = await getPizzaData()    // асинхронный вызов
  const chosenPizza = choosePizza()    // синхронный вызов
  await addPizzaToCart(chosenPizza)    // асинхронный вызов
}
async function selectDrink() {
  const drinkData = await getDrinkData()    // асинхронный вызов
  const chosenDrink = chooseDrink()    // синхронный вызов
  await addDrinkToCart(chosenDrink)    // асинхронный вызов
}
(async () => {
  const pizzaPromise = selectPizza()
  const drinkPromise = selectDrink()
  await pizzaPromise
  await drinkPromise
  orderItems()    // асинхронный вызов
})()
// Задачу можно решить так, как показано выше, но я предпочитаю следующий метод 
(async () => {
  Promise.all([selectPizza(), selectDrink()]).then(orderItems)   // асинхронный вызов
})()

Nun sind die Ausdrücke für Pizza und Getränke in den Funktionen selectPizza()und gruppiert selectDrink(). Innerhalb dieser Funktionen ist die Reihenfolge der Ausführung der Befehle wichtig, da die folgenden Befehle von den Ergebnissen der vorherigen Befehle abhängen. Nachdem die Funktionen vorbereitet sind, rufen wir sie asynchron auf.

Im zweiten Beispiel haben wir es mit einer unbekannten Anzahl von Versprechungen zu tun. Die Lösung dieses Problems ist jedoch sehr einfach. Sie müssen nämlich ein Array erstellen und darin Versprechen platzieren. Dann können Sie mithilfe von Promise.all()das Warten auf die Lösung all dieser Versprechen organisieren.

async function orderItems() {
  const items = await getCartItems()    // асинхронный вызов
  const noOfItems = items.length
  const promises = []
  for(var i = 0; i < noOfItems; i++) {
    const orderPromise = sendRequest(items[i])    // асинхронный вызов
    promises.push(orderPromise)    // синхронный вызов
  }
  await Promise.all(promises)    // асинхронный вызов
}

Zusammenfassung


Wie Sie sehen, wirkt sich das, was auf den ersten Blick als „asynchrone Hölle / Warte auf Hölle“ bezeichnet wird, ziemlich anständig aus, doch hinter dem Wohlbefinden von außen steckt ein negativer Einfluss auf die Leistung. Aus dieser Hölle ist es jedoch nicht so schwer zu entkommen. Es reicht aus, den Code zu analysieren, herauszufinden, welche Aufgaben mit seiner Hilfe gelöst werden können, und die notwendigen Änderungen am Programm vorzunehmen.

Sehr geehrte Leser! Hast du jemals die Hölle asynchron gesehen?


Jetzt auch beliebt: