11. Ereignisgesteuerte Programmierung und asynchrone Funktionen

Eine asynchrone Funktion ist eine Funktion, deren Ausführung initiiert wird, auf deren Ergebnis jedoch nicht gewartet wird. Nach Abschluss der Ausführung löst die asynchrone Funktion ein Ereignis aus und übermittelt ihr Ergebnis über dieses Ereignis.
Diese Arbeitsweise eignet sich gut für die Ausführung in einem Webbrowser. Tatsächlich ist eine in einem Browser ausgeführte Anwendung eine ereignisgesteuerte Anwendung: Die Anwendung reagiert auf Ereignisse, die in erster Linie vom Benutzer ausgelöst werden (Klicks, Mausbewegungen, Texteingaben usw.). JavaScript-Anwendungen, die in einem Browser laufen, interagieren über das HTTP-Protokoll mit externen Diensten. Die nativen HTTP-Funktionen von JavaScript sind asynchron: Sie werden initiiert, und der Empfang einer Antwort vom angeforderten externen Dienst wird durch ein Ereignis signalisiert, das dem von der Anwendung verwalteten Ereignissatz hinzugefügt wird.
Die folgenden Skripte werden von [node.js] und nicht von einem Browser ausgeführt. [node.js] verfügt ebenfalls über ein ereignisgesteuertes Ausführungsmodell:
- [node.js] verwendet, ähnlich wie ein Browser, eine Ereignisschleife zur Ausführung eines Skripts;
- die Ausführung des Hauptcodes des Skripts ist das erste ausgeführte Ereignis;
- wenn dieser Hauptcode asynchrone Aufgaben gestartet hat, wird die Ausführung fortgesetzt, bis diese asynchronen Aufgaben abgeschlossen sind. Diese Aufgaben lösen ein Ereignis aus, wenn sie beendet sind. Diese Ereignisse werden in der Ereignisschleife in eine Warteschlange gestellt;
- das Hauptskript muss diese Ereignisse abonnieren, wenn es die Ergebnisse der asynchronen Aktionen abrufen möchte;
- das Skript ist erst beendet, wenn alle von ihm ausgelösten Ereignisse verarbeitet wurden;
11.1. Skript [async-01]
Das folgende Skript veranschaulicht das Verhalten eines Skripts, das eine asynchrone Aktion enthält.
'use strict';
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// setTimeout arms a 1000 ms timer (2nd parameter) and immediately returns the timer number
// when the timer has run out of 1000 ms, it emits an event which is queued by the runtime
// when the event is processed by the runtime, the function (1st parameter) is executed
setTimeout(function () {
// this code will be executed when the timer reaches value 0
console.log("[fin de l'action asynchrone setTimeout],", heure(débutScript));
}, 1000)
// will be displayed before the internal timer function msg
console.log("[fin du code principal du script],", heure(débutScript));
// time and duration display utility
function heure(début) {
// current time
const now = moment(Date.now());
// time formatting
let result = "heure=" + now.format("HH:mm:ss:SSS");
// is it necessary to calculate a duration?
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// format time + duration
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// result
return result;
}
- Zeile 4: Wir importieren die [moment]-Bibliothek, um Datumsangaben zu formatieren (Zeile 27);
- Zeile 5: Importieren der Bibliothek [sprintf-js] zur Formatierung von Zeiträumen (Zeile 34);
- Zeile 8: Wir erfassen die Startzeit des Skripts;
- Zeile 9: Wir zeigen sie mithilfe der [time]-Methode in den Zeilen 20–34 an;
- Zeilen 14–17: Die Funktion [setTimeout] hat zwei Parameter (f, duration): f ist eine Funktion, die ausgeführt wird, wenn [duration] Millisekunden verstrichen sind;
- Zeile 14: Wenn das Skript läuft, wird die Funktion [setTimeout] ausgeführt:
- Zeile 17: Ein 1000-ms-Timer wird gestartet und der Countdown beginnt, bis er schließlich 0 erreicht. Die Funktion [setTimeout] ist beendet, sobald der Timer initialisiert ist und der Countdown beginnt. Sie wartet nicht auf das Ende des Countdowns. Sie gibt eine ID-Nummer für den verwendeten Timer zurück, und die Ausführung geht zur nächsten Anweisung, Zeile 20, über. Hier wird das Ergebnis von [setTimeout] nicht verwendet;
- Zeile 16: Diese Meldung wird am Ende der 1000-ms-Verzögerung der Funktion [setTimeout] angezeigt;
- Zeilen 15–16: Die Funktion f, der erste Parameter der Funktion [setTimeout], wird am Ende der Verzögerung von 1000 ms ausgeführt. Die Meldung in Zeile 16 wird dann angezeigt;
- Zeile 20: Diese Meldung wird vor der in Zeile 16 angezeigt;
Zeitfunktion:
- Zeile 23: Die Funktion akzeptiert einen optionalen Parameter [time], der die Startzeit einer Operation angibt, deren Dauer sie anzeigen soll;
- Zeilen 25–27: Die aktuelle Zeit wird berechnet und formatiert;
- Zeile 29: Ist der Parameter [start] vorhanden, muss eine Dauer berechnet werden;
- Zeile 30: die Dauer des Vorgangs. Das Ergebnis ist eine Zahl in Millisekunden;
- Zeilen 31–32: Diese Anzahl an Millisekunden wird in Sekunden und Millisekunden aufgeschlüsselt;
- Zeile 34: Die Dauer wird zur Zeit addiert;
Ausführung
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-01.js"
[début du script], heure=09:26:40:238
[fin du code principal du script], heure=09:26:40:246, durée= 0 seconde(s) et 11 millisecondes
[fin de l'action asynchrone setTimeout], heure=09:26:41:249, durée= 1 seconde(s) et 14 millisecondes
[Done] exited with code=0 in 1.672 seconds
- Zeile 4: Wir sehen, dass die asynchrone Aktion [setTimeout] etwa 1 Sekunde nach dem Ende des Hauptcodes des Skripts beendet wurde;
- Zeile 6: Die in Zeile 3 angezeigte Zeit ist der Zeitpunkt, zu dem der Hauptcode beendet ist. Wenn der Hauptcode asynchrone Aufgaben initiiert hat, ist das Skript erst abgeschlossen, wenn alle asynchronen Aufgaben ausgeführt wurden. Die in Zeile 6 angezeigte Dauer ist die gesamte Ausführungszeit des Skripts (Hauptcode + asynchrone Aufgaben);
Mit der Funktion [setTimeout] können wir in einer [node.js]-Umgebung asynchrone Aufgaben simulieren. Tatsächlich verhält sich die Funktion [setTimeout] wie eine asynchrone Aufgabe:
- Sie gibt sofort ein Ergebnis zurück – in diesem Fall eine Timer-ID – unter Verwendung des Standardfunktionsmechanismus (return);
- sie kann später (was oben noch nicht der Fall ist) weitere Ergebnisse über Ereignisse zurückgeben, die dann von der [node.js]-Ereignisschleife verarbeitet werden;
- in den meisten der folgenden Fälle gibt es zwei solcher Ereignisse:
- ein Ereignis, das als [success] bezeichnet werden könnte und von der asynchronen Aufgabe ausgelöst wird, die ihre Aufgabe erfolgreich abgeschlossen hat. Dem ausgelösten Ereignis ist eine Datenangabe – das Ergebnis der Aufgabe – zugeordnet;
- ein Ereignis, das als [failure] bezeichnet werden könnte, das von der asynchronen Aufgabe ausgegeben wird, die ihre Aufgabe nicht abgeschlossen hat. Mit dem ausgegebenen Ereignis ist eine Datenangabe verbunden – typischerweise ein Objekt, das den Fehler beschreibt. Mögliche Fehler bei einer asynchronen Internetaufgabe wären beispielsweise „Netzwerk nicht verfügbar“, „Server existiert nicht“, „Zeitlimit überschritten“, ...
- Der Hauptcode, der eine asynchrone Aufgabe gestartet hat, kann die Ereignisse abonnieren, die diese Aufgabe voraussichtlich auslöst. Wenn eines dieser Ereignisse ausgelöst wird, wird der Hauptcode benachrichtigt und kann die Ausführung einer bestimmten Funktion auslösen, die für die Behandlung des Ereignisses vorgesehen ist. Diese Funktion erhält als Parameter die Daten, die die asynchrone Aufgabe mit dem ausgelösten Ereignis verknüpft hat;
11.2. Skript [async-02]
In diesem Skript sendet die asynchrone Funktion [setTimeout] Ereignisse aus, um Daten an den Code zu übermitteln, der diese abonniert hat.
Für den Zugriff auf [node.js]-Ereignisse sind zusätzliche Bibliotheken erforderlich. Wir wählen die Bibliothek [events], die wir mit [npm] installieren:

Das Skript [async-02] lautet wie folgt:
'use strict';
// asynchronous functions can return a result by emitting an event
// the main code can retrieve these results by subscribing to the events issued
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// an event transmitter
const eventEmitter = new EventEmitter();
// setTimeout sets a 1000 ms timer (2nd parameter) and immediately returns the timer number
// when the timer has run out of 1000 ms, it emits an event which is queued by the runtime
// when the event is processed by the runtime, the function (1st parameter) is executed
setTimeout(function () {
// this code will be executed when the timer reaches value 0
console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
// an event is issued to indicate that a result is available
eventEmitter.emit("timer1Success", { success: 4 });
// another event is issued to indicate that another result is available
eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)
// subscribe to evt [timer1Success]
eventEmitter.on('timer1Success', (result) => {
console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Success]", result, heure(débutScript)));
});
// subscribe to evt [timer1Failure]
eventEmitter.on('timer1Failure', (result) => {
console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Failure]", result, heure(débutScript)));
});
// will be displayed before the msg of evts issued by the function associated with [timer1]
console.log("[fin du code principal du script],", heure(débutScript));
// time and duration display utility
function heure(début) {
// current time
const now = moment(Date.now());
// time formatting
let result = "heure=" + now.format("HH:mm:ss:SSS");
// is it necessary to calculate a duration?
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// format time + duration
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// result
return result;
}
Kommentare
- Zeile 9: Wir importieren die Klasse [EventEmitter] aus der Bibliothek [events]. Das ist neu: Bisher haben wir nur literale Objekte und Funktionen importiert;
- Zeile 15: Wir erstellen einen [node.js]-Event-Emitter, indem wir die Klasse [EventEmitter] mit dem Schlüsselwort [new] instanziieren;
- Zeilen 20–27: die asynchrone Funktion [setTimeout]. Sie löst bei ihrer Ausführung zwei Ereignisse aus:
- Zeile 24: das [timer1Success]-Ereignis mit dem Objekt {success: 4} als zugehörigem Wert;
- Zeile 26: das [timer1Failure]-Ereignis mit dem Objekt {failure: 6} als zugehörigem Wert;
- Eine asynchrone Funktion kann beliebig viele Ereignisse auslösen. Wir haben bereits erwähnt, dass sie meist eines der beiden Ereignisse [success, failure] auslöst, nicht beide, wie wir es hier tun;
- Zeile 20: Die Ausführung von [setTimeout] erfolgt sofort: Ein Timer wird gesetzt und seine ID an den aufrufenden Code zurückgegeben. Die Ereignisse werden später ausgelöst, in diesem Fall 1 Sekunde später;
- Das Auslösen von Ereignissen ist sinnlos, wenn es keinen Code gibt, der sie verarbeitet, sobald sie auftreten. Deshalb muss der Hauptcode beide Ereignisse [timer1Success, timer1Failure] abonnieren, wenn er sie verarbeiten will, insbesondere um die mit diesen Ereignissen verbundenen Daten abzurufen;
- Zeilen 30–32: Der Hauptcode abonniert das Ereignis [timer1Success]. Wenn der [node.js]-Ereignis-Listener dieses Ereignis verarbeitet, ruft er die Funktion auf, die den zweiten Parameter der Methode [eventEmitter.on] bildet, und übergibt ihr die mit dem Ereignis [timer1Success] verbundenen Daten (hier [result] genannt);
- Zeile 31: Die Ereignisbehandlungsfunktion zeigt das JSON der mit dem Ereignis verbundenen Daten sowie die aktuelle Uhrzeit an;
- Zeilen 35–37: Mit ähnlichem Code abonniert der Hauptcode das Ereignis [timer1Failure];
- Das Abonnieren eines Ereignisses (erster Parameter) führt nicht sofort zur Ausführung des Codes der [callback]-Funktion (zweiter Parameter). Diese Funktion wird erst ausgeführt, nachdem das Ereignis eingetreten ist;
- Zeile 40: Der Hauptskriptcode ist beendet, das Skript selbst jedoch noch nicht, da der Hauptcode eine asynchrone Aufgabe gestartet hat. Das gesamte Skript wird erst beendet, wenn diese asynchrone Aufgabe abgeschlossen ist;
Das zeigen die Ergebnisse:
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-02.js"
[début du script], heure=09:34:58:909
[fin du code principal du script], heure=09:34:58:916, durée= 0 seconde(s) et 10 millisecondes
[setTimeout, fin du timer d'1 s], heure=09:34:59:929, durée= 1 seconde(s) et 23 millisecondes
la fonction asynchrone du timer a rendu le résultat [{"success":4}], heure=09:34:59:931, durée= 1 seconde(s) et 25 millisecondes, via l'événement [timer1Success]
la fonction asynchrone du timer a rendu le résultat [{"failure":6}], heure=09:34:59:932, durée= 1 seconde(s) et 26 millisecondes, via l'événement [timer1Failure]
[Done] exited with code=0 in 1.627 seconds
- Zeile 3: Ende des Hauptcodes 10 ms nach Start des Skripts;
- Zeile 4: Beginn der im 1000-ms-Timer gekapselten Funktion, ca. 1 Sekunde nach Skriptstart;
- Zeile 5: Verarbeitung des Ereignisses [‘timer1Success’], 2 ms später;
- Zeile 6: Verarbeitung des Ereignisses [‘timer1Failure’], 1 ms nach dem Ereignis [‘timer1Success’];
- Zeile 8: Ende des globalen Skripts mit einer Gesamtdauer von 1,627 Sekunden;
11.3. Skript [async-03]
Das folgende Skript veranschaulicht einen weiteren Aspekt der [node.js]-Ereignisschleife:
- Die Schleife führt Ereignisse nacheinander aus, in der Regel in der Reihenfolge ihres Eintreffens. Einige Betriebssysteme weisen Ereignissen Prioritäten zu, die dann in der Reihenfolge ihrer Priorität und nicht in der Reihenfolge ihres Eintreffens verarbeitet werden;
- Die Schleife führt jeweils nur ein Ereignis aus. Das nächste Ereignis wird erst verarbeitet, nachdem das vorherige abgeschlossen ist. In einem ereignisgesteuerten System sollten Sie daher vermeiden, Code zu schreiben, der den Prozessor über längere Zeiträume beansprucht, da Ereignisse nicht sofort bei ihrem Auftreten verarbeitet werden, sondern erst später, wenn die Ereignisschleife sie erreicht. Dies führt zu einer Anwendung, die nicht sehr „reaktionsschnell“ ist;
Das Skript [async-03] veranschaulicht dieses Phänomen anhand eines Beispiels:
'use strict';
// asynchronous functions can return a result by emitting an event
// the main code can retrieve these results by subscribing to the events issued
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// an event transmitter
const eventEmitter = new EventEmitter();
// setTimeout sets a 1000 ms timer (2nd parameter) and immediately returns the timer number
// when the timer has run out of 1000 ms, it emits an event which is queued by the runtime
// when the event is processed by the runtime, the function (1st parameter) is executed
setTimeout(function () {
// this code will be executed when the timer reaches value 0
console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
// an event is issued to indicate that a result is available
eventEmitter.emit("timer1Success", { success: 4 });
// another event is issued to indicate that another result is available
eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)
// subscribe to evt [timer1Success]
eventEmitter.on('timer1Success', (result) => {
console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Success]", result, heure(débutScript)));
});
// subscribe to evt [timer1Failure]
eventEmitter.on('timer1Failure', (result) => {
console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Failure]", result, heure(débutScript)));
});
// slightly intensive synchronous code that prevented the main code from completing before the end of [timer1]
for (let i = 0; i < 1000000; i++) {
for (let j = 0; j < 10000; j++) {
i + i ^ 2 + i ^ 3;
}
}
// will be displayed before the msg of evts issued by the function associated with [timer1]
console.log("[fin du script],", heure(débutScript));
// time and duration display utility
function heure(début) {
...
}
Kommentare
- Dieser Code stammt aus dem vorherigen Beispiel [async-02], dem die Zeilen 39–44 hinzugefügt wurden;
- Zeilen 20–27: Die Funktion [setTimeout] wurde so programmiert, dass sie nach einer Verzögerung von einer Sekunde eine interne asynchrone Funktion ausführt. Nach Ablauf dieser Sekunde erfolgt die Ausführung der asynchronen Funktion des Timers nicht sofort: Es wird ein Ereignis in die Ausführungsschleife eingefügt, um diese anzufordern. Ist die Ausführungsschleife mit der Verarbeitung eines anderen Ereignisses beschäftigt, muss die Ausführung der asynchronen Funktion des Timers warten;
- Zeilen 20–27: Sobald die Funktion [setTimeout] ihren Timer auf eine Verzögerung von einer Sekunde eingestellt hat, gibt sie den Prozessor frei und übergibt die Kontrolle an den aufrufenden Code. Der aufrufende Code fährt mit den Zeilen 30–37 fort, bei denen es sich um Ereignisabonnements handelt, deren Ausführungszeit vernachlässigbar ist;
- Der Hauptcode setzt sich mit den Zeilen 40–44 fort, die eine Schleife mit 1.010 Iterationen bilden. Dieser Code wird ausgeführt, wenn der Timer sein Ereignis „Ende der 1-Sekunden-Verzögerung“ auslöst. Dieses Ereignis wird dann in die Ereignisschleife gestellt, muss jedoch warten, bis der Hauptcode des Skripts die Ausführung beendet hat, um eine Chance auf Verarbeitung zu haben;
- Zeile 47: Ende des Hauptcodes des Skripts. Erst nach dieser letzten Anzeige kann das „end“-Ereignis des Timers verarbeitet und die interne asynchrone Funktion von [setTimeout] ausgeführt werden;
Das Skript liefert die folgenden Ergebnisse:
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-03.js"
[début du script], heure=08:55:02:665
[fin du code principal du script], heure=08:55:11:789, durée= 9 seconde(s) et 131 millisecondes
[setTimeout, fin du timer d'1 s], heure=08:55:11:794, durée= 9 seconde(s) et 136 millisecondes
la fonction asynchrone du timer a rendu le résultat [{"success":4}], heure=08:55:11:794, durée= 9 seconde(s) et 136 millisecondes, via l'événement [timer1Success]
la fonction asynchrone du timer a rendu le résultat [{"failure":6}], heure=08:55:11:794, durée= 9 seconde(s) et 136 millisecondes, via l'événement [timer1Failure]
[Done] exited with code=0 in 9.796 seconds
Kommentare
- Zeile 3: Wir sehen, dass die Ausführung des Hauptcodes des Skripts 9 Sekunden gedauert hat. Alle Ereignisse, die während dieser Zeit auftraten, wurden in der Ereignisschleife in die Warteschlange gestellt;
- Zeile 4: Wir sehen, dass das [timer end]-Ereignis 5 ms nach Beendigung des Hauptcodes verarbeitet wurde. Es wurde etwa 1 s nach dem Start des Skripts ausgelöst, musste jedoch weitere 8 s warten, bis es schließlich verarbeitet wurde;
Die wichtigste Erkenntnis aus diesem Beispiel ist, dass in einem ereignisgesteuerten System Code den Prozessor niemals sehr lange beanspruchen sollte. Wenn Sie synchronen Code haben, dessen Ausführung lange dauert, müssen Sie einen Weg finden, ihn in kürzere asynchrone Aufgaben aufzuteilen, die ihre Fertigstellung mit einem Ereignis signalisieren.
11.4. Skript [async-04]
Das Skript [async-04] demonstriert einen weiteren Mechanismus namens [Promise], ein Versprechen auf ein Ergebnis. Dieser Mechanismus macht die explizite Behandlung von [node.js]-Ereignissen überflüssig. Die Behandlung erfolgt implizit, sodass der Entwickler die Existenz dieser Ereignisse ignorieren kann. Ihr Verständnis hilft dem Entwickler jedoch, die Funktionsweise von [Promise] besser zu begreifen, die auf den ersten Blick komplex erscheint.
Der Typ [Promise] ist eine JavaScript-Klasse. Sein Konstruktor akzeptiert eine asynchrone Funktion als Parameter, an die er zwei Parameter übergibt, die traditionell [resolve] und [reject] genannt werden. Sie könnten auch andere Namen haben;
- Der [Promise]-Konstruktor führt zwei Dinge aus:
- Er erzeugt ein Ereignis, um die Ausführung der ihm als Parameter übergebenen Funktion [function(resolve, reject)] auszulösen, wartet jedoch nicht auf deren Ergebnis und gibt sofort ein [Promise]-Objekt an den aufrufenden Code zurück. Dieses Objekt kann vier Zustände annehmen:
- [pending]: Die asynchrone Aktion, die das [Promise] zurückgegeben hat, ist noch nicht abgeschlossen;
- [fulfilled]: Die asynchrone Aktion, die das [Promise] zurückgegeben hat, wurde erfolgreich abgeschlossen;
- [rejected]: Die asynchrone Aktion, die das [Promise] zurückgegeben hat, ist fehlgeschlagen;
- [settled]: Die asynchrone Aktion, die das [Promise] zurückgegeben hat, ist abgeschlossen;
- Er erzeugt ein Ereignis, um die Ausführung der ihm als Parameter übergebenen Funktion [function(resolve, reject)] auszulösen, wartet jedoch nicht auf deren Ergebnis und gibt sofort ein [Promise]-Objekt an den aufrufenden Code zurück. Dieses Objekt kann vier Zustände annehmen:
Wenn der Konstruktor sein Ergebnis zurückgibt, befindet sich das erstellte [Promise]-Objekt im Zustand [pending] und wartet auf die Ergebnisse der asynchronen Funktion;
- (Fortsetzung)
- Die asynchrone Aufgabe in den Zeilen 2–5 wird sofort gestartet. Bei asynchronen Aufgaben handelt es sich meist um Ein-/Ausgabeaufgaben, die sich wie folgt unterteilen:
- Ausführung von synchronem Code, um die E/A-Operation mit einer anderen Komponente, wie z. B. einem Remote-Server, zu initiieren;
- Warten auf eine Antwort von dieser Komponente;
- Verarbeitung dieser Antwort;
- Die asynchrone Aufgabe in den Zeilen 2–5 wird sofort gestartet. Bei asynchronen Aufgaben handelt es sich meist um Ein-/Ausgabeaufgaben, die sich wie folgt unterteilen:
Phase 2 – das Warten auf die externe Komponente – ist am ressourcenintensivsten. Anstatt zu warten:
- wird der Empfang der von der externen Komponente angeforderten Daten durch ein Ereignis signalisiert;
- im synchronen Code, der auf Phase 1 folgt (Zeile 7 des Beispielcodes), abonnieren wir dieses Ereignis und kehren dann zu einem späteren Zeitpunkt zur [node.js]-Ereignisschleife zurück. Das nächste Ereignis in der Liste der ausstehenden Ereignisse wird dann verarbeitet;
- während Phase 2 findet eine parallele Ausführung statt, jedoch auf verschiedenen Geräten:
- der Prozessor für die Ereignisschleife;
- eine externe Komponente (Festplatte, Datenbank, Remote-Server) zum Abrufen der angeforderten Daten;
- Am Ende von Phase 2, sobald die E/A-Operation die angeforderten Daten erhalten hat, wird ein Ereignis ausgelöst, um anzuzeigen, dass das E/A-Ergebnis verfügbar ist. Dieses Ereignis reiht sich dann in die anderen in der Ereigniswarteschlange ein;
- Wenn es an der Reihe ist, wird es verarbeitet. Die diesem Ereignis zugeordnete Funktion (Zeile 7 des Beispielcodes) wird dann ausgeführt;
Diese Arbeitsweise hilft, Ausfallzeiten zu vermeiden: die Situation, in der der Prozessor auf eine Antwort von einem Gerät wartet, das langsamer ist als er selbst;
- (Fortsetzung)
- Sobald die asynchrone Aufgabe in den Zeilen 2 und 5 gestartet wurde und ihre Arbeit abgeschlossen hat, kann sie ein Ergebnis an den aufrufenden Code zurückgeben, indem sie die beiden Funktionen [resolve, reject] verwendet, die der [Promise]-Konstruktor ihr als Parameter übergeben hat. Die Konvention lautet wie folgt:
- Die asynchrone Aufgabe signalisiert Erfolg über [resolve(result)]. Dies entspricht dem Hinzufügen eines Ereignisses zur [node.js]-Ereignisschleife, das als [resolved] bezeichnet werden könnte, mit [result] als zugehörigen Daten;
- Die asynchrone Aufgabe signalisiert einen Fehler über [reject(error)]. Dies entspricht dem Hinzufügen eines Ereignisses zur [node.js]-Ereignisschleife, das [rejected] heißen könnte, mit [error] als zugehörigen Daten – typischerweise ein Objekt, das den aufgetretenen Fehler detailliert beschreibt;
- Der aufrufende Code muss daher diese beiden Ereignisse abonnieren, um benachrichtigt zu werden, sobald das Ergebnis der asynchronen Funktion verfügbar ist;
- Sobald die asynchrone Aufgabe in den Zeilen 2 und 5 gestartet wurde und ihre Arbeit abgeschlossen hat, kann sie ein Ergebnis an den aufrufenden Code zurückgeben, indem sie die beiden Funktionen [resolve, reject] verwendet, die der [Promise]-Konstruktor ihr als Parameter übergeben hat. Die Konvention lautet wie folgt:
Nachdem die in [Promise] gekapselte asynchrone Aufgabe ausgeführt wurde, ändert sich der Status des vom Konstruktor [Promise(…)] zurückgegebenen [promise]-Objekts:
- Das [resolved]-Ereignis ändert seinen Status von [pending] zu [resolved];
- das [rejected]-Ereignis wechselt vom Status [pending] zu [rejected];
Das Abonnieren der Ereignisse [resolved] und [rejected] der asynchronen Aufgabe erfolgt mithilfe von Methoden der Klasse [Promise] mit folgender Syntax:
promise.then(f1).catch(f2).finally(f3);
wobei:
- f1 eine Funktion ist, die ausgeführt wird, wenn sich der Status von [promise] von [pending] zu [resolved] ändert, d. h. wenn die asynchrone Aufgabe ihre Arbeit erfolgreich abgeschlossen hat. Sie erhält den Wert [result] als Parameter, der von der Anweisung [resolve(result)] der asynchronen Aufgabe übergeben wird;
- f2 ist eine Funktion, die ausgeführt wird, wenn sich der Status von [promise] von [pending] zu [rejected] ändert, d. h. wenn die asynchrone Aufgabe ihre Arbeit nicht erfolgreich abgeschlossen hat. Sie erhält den Wert [error] als Parameter, der von der Anweisung [reject(error)] der asynchronen Aufgabe übergeben wird;
- f3 ist eine Funktion, die nach der Ausführung der Methoden [then] oder [catch] aufgerufen wird, sodass sie immer ausgeführt wird. Sie erhält keine Parameter;
Diese Syntax verbirgt die Ereignisse, die wir abonnieren, vollständig. Es handelt sich jedoch um ein Abonnement, und wie im vorherigen Beispiel werden die Funktionen [f1, f2, f3] nicht sofort ausgeführt. Diese werden ausgeführt – oder auch nicht –, wenn eines der Ereignisse [resolved, rejected], die wir abonnieren, eintritt.
Das Skript [async-04] veranschaulicht diesen Mechanismus:
'use strict';
// it is possible to obtain the results (success, failure) of an asynchronous function
// without explicitly using events thanks to the [Promise] class
// this class implicitly uses events, but these are not visible in the code
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure(débutScript));
// definition of an asynchronous task using a promise [Promise]
// the asynchronous task is the [Promise] constructor parameter
const débutPromise1 = moment(Date.now());
const promise1 = new Promise(function (resolve) {
// log
console.log("[début fonction asynchrone de promise1],", heure(débutPromise1));
// asynchronous code
setTimeout(function () {
console.log("[fin fonction asynchrone de promise1],", heure(débutPromise1));
// the asynchronous task returns a result with the [resolve] function
// the promise is fulfilled
resolve('[réussite]');
}, 1000)
});
// we can know the result of the promise [promise1]
// when it has been resolved or rejected
// the following instruction is a subscription to the [resolved] event via the [then] method
// and to the [rejected] event via the [catch] method
// the [finally] method is executed whether after a then or a catch
promise1.then(result => {
// promise success stories [evt resolved]
console.log(sprintf("[promise1.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
// error case [evt rejected]
console.log(sprintf("[promise1.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
// executed in all cases
console.log("[promise1.finally]", heure(débutPromise1));
});
// definition of an asynchronous task using a promise [Promise]
const débutPromise2 = moment(Date.now());
const promise2 = new Promise(function (resolve, reject) {
// log
console.log("[début fonction asynchrone de promise2],", heure(débutPromise1));
// asynchronous task
setTimeout(function () {
console.log("[fin fonction asynchrone de promise2],", heure(débutPromise2));
// the asynchronous task returns a result with the [reject] function
// the promise is lost
reject('[échec]');
}, 2000)
});
// we can know the result of the promise [promise2]
// when it has been resolved or rejected
promise2.then(result => {
// promise success stories [evt resolved]
console.log(sprintf("[promise2.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
// error case [evt rejected]
console.log(sprintf("[promise2.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
// executed in all cases
console.log(sprintf("[promise2.finally], %s", heure(débutPromise2)));
});
// will be displayed before the msg of asynchronous functions and those of associated events
console.log("[fin du code principal du script],", heure(débutScript));
// utility
function heure(début) {
// current time
const now = moment(Date.now());
// time formatting
let result = "heure=" + now.format("HH:mm:ss:SSS");
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// formatting duration
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// result
return result;
}
Kommentare
- Zeilen 18–28: Erstellung eines [Promise promise1]. Seine asynchrone Funktion gibt ihr Ergebnis nach einer Sekunde über ein Ereignis zurück. Sobald dieser asynchrone Vorgang gestartet ist (Timer wird gestartet), warten wir nicht auf die Rückgabe des Ergebnisses, sondern fahren sofort mit dem Code in Zeile 35 fort;
- Zeilen 35–44: Wir abonnieren die beiden Ereignisse [resolved, rejected], die die interne asynchrone Funktion von [promise1] auslösen kann;
- Zeilen 46–71: Wir wiederholen dieselbe Code-Sequenz wie zuvor für ein zweites Promise [promise2];
- Zeile 74: Der Hauptteil des Skripts ist ausgeführt, das Skript als Ganzes jedoch noch nicht, da zwei asynchrone Aktionen initiiert wurden. Wir kehren zur Ereignisschleife zurück, wo irgendwann eines der Ereignisse [resolved, rejected] für die Promises [promise1, promise2] eintreten wird. Es wird dann verarbeitet;
- dann kehren wir zur Ereignisschleife zurück. Und dort wird das zweite [resolved, rejected]-Ereignis der Promises [promise1, promise2] behandelt, sobald es eintritt;
Ausführung
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-04.js"
[début du script], heure=09:39:05:950, durée= 0 seconde(s) et 3 millisecondes
[début fonction asynchrone de promise1], heure=09:39:05:958, durée= 0 seconde(s) et 0 millisecondes
[début fonction asynchrone de promise2], heure=09:39:05:959, durée= 0 seconde(s) et 1 millisecondes
[fin du code principal du script], heure=09:39:05:960, durée= 0 seconde(s) et 13 millisecondes
[fin fonction asynchrone de promise1], heure=09:39:06:977, durée= 1 seconde(s) et 19 millisecondes
[promise1.then], heure=09:39:06:980, durée= 1 seconde(s) et 21 millisecondes, result=[réussite]
[promise1.finally] heure=09:39:06:982, durée= 1 seconde(s) et 24 millisecondes
[fin fonction asynchrone de promise2], heure=09:39:07:976, durée= 2 seconde(s) et 17 millisecondes
[promise2.catch], heure=09:39:07:978, durée= 2 seconde(s) et 19 millisecondes, result=[échec]
[promise2.finally], heure=09:39:07:980, durée= 2 seconde(s) et 21 millisecondes
[Done] exited with code=0 in 2.589 seconds
Kommentare
- Zeile 3: Die asynchrone Funktion von [promise1] wird gestartet, aber wir warten nicht auf ihre Beendigung, die durch ein Ereignis signalisiert wird;
- Zeile 4: Die asynchrone Funktion von [promise2] wird gestartet, aber wir warten nicht auf ihren Abschluss, der durch ein Ereignis signalisiert wird;
- Zeile 5: Ende des Hauptcodes und Rückkehr zur Ereignisschleife;
- Zeile 6: Verarbeitung des Ereignisses [Ende der asynchronen Funktion von promise1]. Der Status von [promise1] ändert sich zu [resolved]. Ein Ereignis signalisiert dies;
- Zeile 7: Da [promise2] seine Arbeit noch nicht beendet hat, wird das Ereignis [promise1 resolved], das gerade zur Schleife hinzugefügt wurde, von der Methode [promise1.then] und anschließend von der Methode [promise.finally] verarbeitet (Zeile 8);
- Zeilen 9–11: Der gleiche Mechanismus tritt ein, wenn [promise2] vom Status [pending] in den Status [resolved] wechselt;
11.5. Skript [async-05]
Kehren wir zum Code für den Konstruktor des [Promise]-Objekts zurück:
Zeile 2: Die asynchrone Aufgabe des [Promise] wird gestartet. Oftmals benötigt sie mehr Parameter als nur die [resolve, reject]-Parameter, die ihr von der Funktion übergeben werden, die sie kapselt. In diesem Fall kapseln wir die Erstellung des [Promise] in eine Funktion, die ihm die Parameter übergibt, die seine asynchrone Funktion benötigt:
Das folgende Skript:
- definiert zwei asynchrone Funktionen, die ein [Promise] zurückgeben;
- startet deren Ausführung parallel und wartet, bis beide beendet sind, bevor eine bestimmte Aufgabe ausgeführt wird;
'use strict';
// we can define asynchronous functions that render a [Promise] type
// they can then be tagged with the keyword [async]
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// an asynchronous function that renders a promise [Promise]
function async01(p1) {
return new Promise((resolve) => {
console.log("[début de la tâche asynchrone async01]");
// the asynchronous task
const débutAsync01 = moment(Date.now());
setTimeout(function () {
console.log("[fin de la tâche asynchrone async01],", heure(débutAsync01));
// the asynchronous task can render a complex result
resolve({
prop1: [10, 20, 30],
prop2: "abcd",
prop3: p1,
});
}, 1000)
});
}
// an asynchronous function that renders a promise [Promise]
function async02(p1, p2) {
return new Promise(resolve => {
console.log("[début de la tâche asynchrone async02]");
// asynchronous task
const débutAsync02 = moment(Date.now());
setTimeout(function () {
console.log("[fin de la tâche asynchrone async02],", heure(débutAsync02));
// the asynchronous task can render a complex result
resolve({
prop1: [11, 21, 31],
prop2: "xyzt",
prop3: p1 + p2
});
}, 2000)
})
}
// run the two asynchronous functions in parallel
// and wait for them both to finish
// the then executes only if both functions have issued the [resolved] event
// the catch is executed as soon as one of the two functions issues the [rejected] event
Promise.all([async01(10), async02(10, 20)])
// the result is an array [result1, result2] where [result1] is the result emitted by a [resolve] of [async01]
// and [result2] the result emitted by a [resolve] of [async02]
.then(result => {
console.log(sprintf("[promise-all success], %s, result=%j", heure(débutScript), result));
})
// error is the result emitted by the first [reject] of one of the two asynchronous functions
.catch(error => {
console.log(sprintf("[promise-all error], %s, erreur=%j", heure(débutScript), error));
})
// finally is executed after then or catch
.finally(() => {
console.log(sprintf("[promise-all finally], %s", heure(débutScript)));
});
// will be displayed before msgs for asynchronous functions and associated events
console.log("[fin du code principal du script],", heure(débutScript));
// utility
function heure(début) {
// current time
const now = moment(Date.now());
// time formatting
let result = "heure=" + now.format("HH:mm:ss:SSS");
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// formatting duration
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// result
return result;
}
Kommentare
- Zeilen 15–30: Wir definieren eine Funktion [async01], die ihr Ergebnis nach 1 Sekunde über ein Timer-Ereignis zurückgibt. Die Funktion [async01] wird im Ergebnis in Zeile 26 verwendet;
- Zeilen 33–47: Wir verfahren ebenso mit einer Funktion [async02], die ihr Ergebnis nach 2 Sekunden über ein Timer-Ereignis zurückgibt. Die Funktion [async02] nimmt zwei Parameter entgegen, die in ihrem Ergebnis in Zeile 44 verwendet werden;
- Wenn die beiden Funktionen [async01] und [async02] aufgerufen werden:
- wird gestartet;
- werden zwei Promises [promise1, promise2] an den aufrufenden Code zurückgegeben;
- kehrt die Ausführung dann zum aufrufenden Code zurück, der weiterläuft;
- Nach etwa 1 Sekunde sendet [async01] ein Ereignis aus, um anzuzeigen, dass sie ihre Arbeit abgeschlossen hat. Das betreffende Ereignis wird zusammen mit dem von [async01] übergebenen Ergebnis in die mit dem Ergebnis verknüpfte Ereignisschleife eingereiht;
- Nach etwa 2 Sekunden läuft derselbe Prozess für [async02] ab;
- Zeile 54: Erst jetzt werden die asynchronen Funktionen [async01, async02] ausgeführt (Bezeichnungen async01(10) und async02(10,20)). Sie werden innerhalb eines Arrays ausgeführt, das als Parameter an die Methode [Promise.all] übergeben wird. Wir wissen, dass [async01, async02] beide ein Promise an den aufrufenden Code zurückgeben. Daher ist der Parameter von [Promise.all] ein Array aus zwei Promises;
- [Promise.all([promise1, promise2, …, promisen]).then(f1).catch(f2).finally(f3)] ist ein Ereignis-Abonnement:
- [Promise.all] ist vom Typ [Promise];
- die Funktion [f1] der [then]-Methode wird ausgeführt, wenn alle Promises [promise1, promise2, …, promisen] im Parameter-Array der [all]-Methode vom Status [pending] in den Status [resolved] übergegangen sind. Mit anderen Worten: [f1] wird ausgeführt, wenn alle Promises im Array erfolgreich abgeschlossen wurden;
- Die Funktion [f2] der [catch]-Methode wird ausgeführt, sobald eines der Promises im Array vom Status [pending] in den Status [rejected] wechselt. Mit anderen Worten: [f2] wird ausgeführt, sobald eines der Promises im Array fehlschlägt;
- Die Funktion [f3] der Methode [finally] wird nach der Ausführung einer der [then, catch]-Methoden ausgeführt, sodass sie immer ausgeführt wird;
Die Ausführung des Codes liefert folgende Ergebnisse:
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-05.js"
[début du script], heure=12:17:17:367
[début de la tâche asynchrone async01]
[début de la tâche asynchrone async02]
[fin du code principal du script], heure=12:17:17:375, durée= 0 seconde(s) et 10 millisecondes
[fin de la tâche asynchrone async01], heure=12:17:18:391, durée= 1 seconde(s) et 17 millisecondes
[fin de la tâche asynchrone async02], heure=12:17:19:389, durée= 2 seconde(s) et 14 millisecondes
[promise-all success], heure=12:17:19:390, durée= 2 seconde(s) et 25 millisecondes, result=[{"prop1":[10,20,30],"prop2":"abcd","prop3":10},{"prop1":[11,21,31],"prop2":"xyzt","prop3":30}]
[promise-all finally], heure=12:17:19:392, durée= 2 seconde(s) et 27 millisecondes
[Done] exited with code=0 in 2.572 seconds
- Zeilen 6–7: Die beiden asynchronen Aufgaben [async01, async02] werden gestartet. Sie laufen parallel. Nicht die Ausführung ihres Codes erfolgt parallel, sondern das jeweilige Warten auf die angeforderten Daten geschieht gleichzeitig;
- Zeile 5: Der Hauptteil des Skripts ist beendet. Jetzt müssen wir nur noch warten, bis die beiden asynchronen Aufgaben [async01, async02] abgeschlossen sind;
- Zeile 6: Die asynchrone Aufgabe [async01] wird etwa 1 Sekunde nach ihrem Start abgeschlossen. Sie gibt ein Ergebnis mithilfe der Funktion [resolve] zurück, sodass sich ihr Promise im Array in Zeile 56 des Codes vom Status [pending] in [resolved] ändert. Dies reicht nicht aus, um die Methode [then] in den Zeilen 59–60 des Codes auszulösen;
- Zeile 7: Die asynchrone Aufgabe [async02] wird etwa 2 Sekunden nach ihrem Start abgeschlossen. Sie gibt ein Ergebnis mithilfe der Funktion [resolve] zurück, sodass sich ihr Promise im Array in Zeile 56 des Codes vom Status [pending] in [resolved] ändert. Die Methode [then] wird ausgeführt, sobald die Ereignisschleife dies zulässt;
- Zeile 8: Die [then]-Methode von [Promise.all] wird ausgeführt. Sie erhält als Parameter ein Array [result1, result2], wobei [result1] das von [async01] zurückgegebene Ergebnis ist und [result2] das von [async02] zurückgegebene Ergebnis;
- Zeile 9: Die [finally]-Methode von [Promise.all] wird ausgeführt;
11.6. Skript [async-06]
Dieses neue Skript zeigt, wie die kombinierte Verwendung der Schlüsselwörter [async / await] asynchronen Code ermöglicht, der synchronem Code ähnelt. Die Ereignisbehandlung ist vollständig verborgen, wodurch der Code leichter verständlich wird.
Wir greifen das vorherige Beispiel mit den folgenden Änderungen wieder auf:
- Wir fügen eine dritte asynchrone Funktion [async03] hinzu, die ihr Ergebnis mithilfe der Methode [Promise.reject] zurückgibt und damit der Ereignisschleife signalisiert, dass sie ihre Aufgabe nicht „erfolgreich“ abgeschlossen hat;
- wir führen die drei asynchronen Funktionen [async01, async02, async03] nacheinander aus. Im vorherigen Beispiel hatten wir die asynchronen Funktionen [async01, async02] parallel ausgeführt;
- Vor der Einführung der Schlüsselwörter [async/await] wurde die sequenzielle Ausführung asynchroner Aktionen mithilfe verschachtelter [Promise]-Objekte erreicht. Immer wenn auf diese Weise mehrere asynchrone Aktionen ausgeführt werden mussten, stieg die Anzahl der Promises entsprechend an, und der Code wurde weniger lesbar;
- Mit den Schlüsselwörtern [async/await] erfolgt die sequenzielle Ausführung asynchroner Aufgaben mit einer Syntax, die der synchronen Aufgabenausführung ähnelt:
// asynchronous function - using async / await
async function main() {
// sequential execution of asynchronous tasks
try {
// execution while waiting for [async01]
const result1 = await async01(...);
console.log("[async01 result]=", result1);
// execution while waiting for [async02]
const result2 = await async02(...);
console.log("[async02 result]=", result2);
// execution while waiting for [async03]
const result3 = await async03(...);
console.log("[async03 result]=", result3);
} catch (error) {
// one of the asynchronous actions has failed
console.log(sprintf("[sequential error]= %j, %s", error));
} finally {
// completed
console.log("[fin exécution séquentielle des tâches asynchrones],");
}
- Zeile 6: Die asynchrone Funktion [async01] wird gestartet (mit dem Schlüsselwort await) und wir warten darauf, dass sie ihr Ergebnis über eine der Methoden [Promise.resolve, Promise.reject] zurückgibt. Dies ist also ein blockierender Vorgang;
- Zeile 6: Das Schlüsselwort [await] wandelt den asynchronen Vorgang [async01] in einen blockierenden Vorgang um. Wir wissen, dass der Vorgang [async01] auf zwei Arten ein Ergebnis zurückgibt:
- Sie gibt fast sofort ein [Promise]-Objekt an den aufrufenden Code zurück;
- anschließend veröffentlicht es ein Ergebnis über die Methoden [Promise.resolve, Promise.reject] an die Ereignisschleife. Es ist dieses letztere Ergebnis, das [result1] in Zeile 6 abruft. Die ereignisgesteuerte Abwicklung der Aktion [async01] ist unsichtbar geworden;
- wenn das Ergebnis [result] von [async01] über [Promise.resolve(result)] veröffentlicht wird, wird es in Zeile 6 [result1] zugewiesen und die Ausführung wird in Zeile 7 fortgesetzt;
- Wenn das Ergebnis von [async01] über [Promise.reject] aufgelöst wird, löst dies eine Ausnahme aus und die Codeausführung springt zu Zeile 14, dem catch-Block. Der Parameter der [catch]-Klausel ist das Fehlerobjekt (error), das von [async01] mithilfe des Ausdrucks [Promise.reject(error)] aufgelöst wird. Die asynchrone Aufgabe kann den Fehler auch über ein [throw(error)] auslösen. Das [error]-Objekt ist dasjenige, das in [catch(error)] abgefangen wird;
- das Schlüsselwort [await] muss sich innerhalb einer Funktion befinden, der das Schlüsselwort [async] vorangestellt ist (Zeile 2). Dieses Schlüsselwort gibt an, dass die Funktion [main] eine asynchrone Funktion ist;
- im Ausdruck [await f(…)] muss [f] eine asynchrone Funktion sein, die ein [Promise]-Objekt an den aufrufenden Code zurückgibt;
- Das Gleiche tun wir für die asynchronen Aktionen [async02] in Zeile 9 und [async03] in Zeile 12;
Unter Verwendung der Schlüsselwörter [async / await] ist es möglich, asynchrone Aufgaben parallel auszuführen, indem man die folgende Syntax verwendet:
try {
// parallel execution of asynchronous tasks
const result = await Promise.all([async01(...), async02(...), async03(...)]);
console.log(sprintf("[parallel success], %s, result=%j", heure(débutParallel), result));
} catch (error) {
// one of the asynchronous actions has failed
console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
} finally {
// completed
console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
}
- Zeile 3: Wir haben eine blockierende Operation: Wir warten darauf, dass die drei asynchronen Aufgaben im Array [async01(..), async02(..), async03(..)] ihre Ergebnisse über eine der Methoden [Promise.resolve, Promise.reject] in der Ereignisschleife veröffentlichen;
- Wenn die drei asynchronen Aufgaben ihre Ergebnisse mit [Promise.resolve] veröffentlichen, ist die Konstante [result] das Array [result1, result2, result3], wobei:
- [result1] das von [async01] mithilfe des Ausdrucks [Promise.resolve(result1)] veröffentlichte Ergebnis ist;
- [result2] das von [async02] mithilfe des Ausdrucks [Promise.resolve(result2)] veröffentlichte Ergebnis ist;
- [result3] das von [async03] mithilfe des Ausdrucks [Promise.resolve(result3)] veröffentlichte Ergebnis ist;
- wenn eine der drei Aufgaben ihr Ergebnis mithilfe des Ausdrucks [Promise.reject(error)] veröffentlicht, tritt eine Ausnahme auf;
- Die Konstante [result] in Zeile 3 erhält keinen Wert;
- die Ausführung springt direkt zum [catch]-Block in Zeile 5;
- der Parameter (error) des catch-Blocks ist das (error)-Objekt, das durch den Ausdruck [Promise.reject(error)] ausgegeben wird;
Durch die Kombination dieser beiden Syntaxen können wir asynchrone Aufgaben entweder sequenziell oder parallel ausführen, wobei wir durchgehend eine Syntax verwenden, die der von synchronem Code ähnelt. Wir sollten daher diese Syntax bevorzugen, die wesentlich lesbarer ist als die vorherigen. Diese [async/await]-Syntax ist erst seit ECMAScript Version 6 verfügbar. Es gibt immer noch viel JavaScript-Code, der Promises [Promise] verwendet. Deshalb ist es auch wichtig zu verstehen, wie diese funktionieren.
Der vollständige Code für das [async-06]-Skript lautet wie folgt:
'use strict';
// parallel or sequential execution of several asynchronous tasks
// with the keywords async / await
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// start
const débutScript = moment(Date.now());
console.log("[début du code principal du script],", heure());
// an asynchronous function rendering a [Promise]
function async01(débutAsync01) {
return new Promise(function (resolve) {
console.log("[début fonction asynchrone async01],", heure());
// asynchronous function
setTimeout(function () {
console.log("[fin fonction asynchrone async01],", heure(débutAsync01));
// asynchronous action can make a result complex
// here success
resolve({
prop1: [11, 21, 31],
prop2: "abcd"
});
}, 1000)
});
}
// an asynchronous function rendering a [Promise]
function async02(débutAsync02) {
console.log("[début fonction asynchrone async02],", heure());
return new Promise(function (resolve) {
// asynchronous function
setTimeout(function () {
console.log("[fin fonction asynchrone async02],", heure(débutAsync02));
// asynchronous action can make a result complex
// here success
resolve({
prop1: [12, 22, 32],
prop2: "xyzt"
});
}, 2000)
})
}
// an asynchronous function rendering a [Promise]
function async03(débutAsync03) {
console.log("[début fonction asynchrone async03],", heure());
return new Promise((resolve, reject) => {
// asynchronous function
setTimeout(function () {
console.log("[fin fonction asynchrone async03],", heure(débutAsync03));
// asynchronous action can make a result complex
// here failure
reject({
prop1: [13, 23, 33],
prop2: "échec"
});
}, 3000)
})
}
// asynchronous function - using async / await
async function main() {
const débutSequential = moment(Date.now());
// sequential execution of asynchronous tasks
console.log("------------ exécution séquentielle des tâches asynchrones lancée ------------------------")
try {
// execution while waiting for [async01]
const débutAsync01 = moment(Date.now());
const result1 = await async01(débutAsync01);
console.log("[async01 result]=", result1);
// execution while waiting for [async02]
const débutAsync02 = moment(Date.now());
console.log("début async02-------------", heure());
const result2 = await async02(débutAsync02);
console.log("[async02 result]=", result2);
// execution while waiting for [async03]
const débutAsync03 = moment(Date.now());
console.log("début async03-------------", heure());
const result3 = await async03(débutAsync03);
console.log("[async03 result]=", result3);
} catch (error) {
// one of the asynchronous actions has failed
console.log(sprintf("[sequential error]= %j, %s", error, heure(débutSequential)));
} finally {
// completed
console.log("[fin exécution séquentielle des tâches asynchrones],", heure(débutSequential));
}
const débutParallel = moment(Date.now());
// parallel execution of asynchronous tasks
console.log("------------ exécution parallèle des tâches asynchrones lancée ------------------------")
try {
const result = await Promise.all([async01(débutParallel), async02(débutParallel), async03(débutParallel)]);
console.log(sprintf("[parallel success], %s, result=%j", heure(débutParallel), result));
} catch (error) {
// one of the asynchronous actions has failed
console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
} finally {
// completed
console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
}
// completed
console.log("[fin de la fonction main],", heure(débutSequential));
}
// execution asynchronous function main
main();
// will be displayed before the various msgs for asynchronous functions and their events
console.log("[fin du code principal du script],", heure(débutScript));
// utility
function heure(début) {
// current time
const now = moment(Date.now());
// time formatting
let result = "heure=" + now.format("HH:mm:ss:SSS");
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// formatting duration
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// result
return result;
}
Die Ausführungsergebnisse lauten wie folgt:
[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-06.js"
[début du code principal du script], heure=15:02:00:152
------------ exécution séquentielle des tâches asynchrones lancée ------------------------
[début fonction asynchrone async01], heure=15:02:00:161
[fin du code principal du script], heure=15:02:00:164, durée= 0 seconde(s) et 15 millisecondes
[fin fonction asynchrone async01], heure=15:02:01:165, durée= 1 seconde(s) et 4 millisecondes
[async01 result]= { prop1: [ 11, 21, 31 ], prop2: 'abcd' }
début async02------------- heure=15:02:01:253
[début fonction asynchrone async02], heure=15:02:01:254
[fin fonction asynchrone async02], heure=15:02:03:265, durée= 2 seconde(s) et 12 millisecondes
[async02 result]= { prop1: [ 12, 22, 32 ], prop2: 'xyzt' }
début async03------------- heure=15:02:03:268
[début fonction asynchrone async03], heure=15:02:03:268
[fin fonction asynchrone async03], heure=15:02:06:285, durée= 3 seconde(s) et 18 millisecondes
[sequential error]= {"prop1":[13,23,33],"prop2":"échec"}, heure=15:02:06:289, durée= 6 seconde(s) et 129 millisecondes
[fin exécution séquentielle des tâches asynchrones], heure=15:02:06:291, durée= 6 seconde(s) et 131 millisecondes
------------ exécution parallèle des tâches asynchrones lancée ------------------------
[début fonction asynchrone async01], heure=15:02:06:292
[début fonction asynchrone async02], heure=15:02:06:293
[début fonction asynchrone async03], heure=15:02:06:294
[fin fonction asynchrone async01], heure=15:02:07:294, durée= 1 seconde(s) et 2 millisecondes
[fin fonction asynchrone async02], heure=15:02:08:298, durée= 2 seconde(s) et 6 millisecondes
[fin fonction asynchrone async03], heure=15:02:09:297, durée= 3 seconde(s) et 5 millisecondes
[parallel error], heure=15:02:09:298, durée= 3 seconde(s) et 6 millisecondes, erreur={"prop1":[13,23,33],"prop2":"échec"}
[fin exécution parallèle des tâches asynchrones],heure=15:02:09:299, durée= 3 seconde(s) et 7 millisecondes
[fin de la fonction main], heure=15:02:09:300, durée= 9 seconde(s) et 140 millisecondes
[Done] exited with code=0 in 9.668 seconds