Skip to content

11. Gebeurtenisgestuurde programmering en asynchrone functies

Image

Een asynchrone functie is een functie waarvan de uitvoering wordt gestart, maar waarvan het resultaat niet wordt afgewacht. Wanneer de uitvoering is voltooid, genereert de asynchrone functie een gebeurtenis en geeft het resultaat via deze gebeurtenis door.

Deze werkwijze is zeer geschikt voor uitvoering binnen een webbrowser. Een applicatie die binnen een browser wordt uitgevoerd, is namelijk een gebeurtenisgestuurde applicatie: de applicatie reageert op gebeurtenissen, die voornamelijk door de gebruiker worden veroorzaakt (klikken, muisbewegingen, tekstinvoer, …). JavaScript-toepassingen die in een browser worden uitgevoerd, moeten via het protocol HTTP communiceren met externe diensten. De native JavaScript-functies van HTTP zijn asynchroon: ze worden gestart en het ontvangen van het antwoord van de aangeroepen externe dienst wordt gemeld door een gebeurtenis die wordt toegevoegd aan de verzameling gebeurtenissen die door de applicatie worden beheerd.

De volgende scripts worden uitgevoerd door [node.js] en niet door een browser. Ook [node.js] heeft een gebeurtenisgestuurde uitvoeringsmodus:

  • [node.js] gebruikt, net als een browser, een gebeurtenislus om een script uit te voeren;
  • de uitvoering van de hoofdcode van het script is de eerste gebeurtenis die wordt uitgevoerd;
  • als deze hoofdcode asynchrone taken heeft gestart, gaat de uitvoering door totdat deze asynchrone taken zijn voltooid. Deze taken genereren een gebeurtenis wanneer ze zijn voltooid. Deze gebeurtenissen worden in de wachtrij van de gebeurtenissenlus geplaatst;
  • het hoofdscript moet zich op deze gebeurtenissen abonneren als het de resultaten van de asynchrone acties wil ophalen;
  • het script is pas voltooid wanneer alle door het script gegenereerde gebeurtenissen zijn verwerkt;

11.1. script [async-01]

Het volgende script laat zien hoe een script met een asynchrone actie werkt.


'use strict';

// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';

// begin
const débutScript = moment(Date.now());
console.log("[début du script],", heure());

// setTimeout start een timer van 1000 ms (2e parameter) en retourneert onmiddellijk het nummer van deze timer
// wanneer de timer de 1000 ms heeft bereikt, genereert deze een gebeurtenis die in de wachtrij van de runtime wordt geplaatst
// wanneer de gebeurtenis door de runtime wordt verwerkt, wordt de functie (eerste parameter) uitgevoerd
setTimeout(function () {
  // deze code wordt uitgevoerd wanneer de timer de waarde 0 heeft bereikt
  console.log("[fin de l'action asynchrone setTimeout],", heure(débutScript));
}, 1000)

// wordt weergegeven vóór het bericht van de interne functie van de timer
console.log("[fin du code principal du script],", heure(débutScript));

// hulpprogramma voor het weergeven van tijd en duur
function heure(début) {
  // huidige tijd
  const now = moment(Date.now());
  // tijdopmaak
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  // moet er een duur worden berekend?
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // opmaak van tijd + duur
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // resultaat
  return result;
}
  • regel 4: de bibliotheek [moment] wordt geïmporteerd om de datums te kunnen opmaken (regel 27);
  • regel 5: de bibliotheek [sprintf-js] wordt geïmporteerd om de tijdsduur op te maken (regel 34);
  • regel 8: we noteren het starttijdstip van het script;
  • regel 9: deze wordt weergegeven met behulp van de methode [heure] uit de regels 20-34;
  • regels 14-17: de functie [setTimeout] heeft twee parameters (f, duur): f is een functie die wordt uitgevoerd wanneer [durée] milliseconden zijn verstreken;
  • regel 14: bij het uitvoeren van het script wordt de functie [setTimeout] uitgevoerd:
    • regel 17: er wordt een timer van 1000 ms gestart en het aftellen begint totdat deze later 0 bereikt. De functie [setTimeout] is voltooid zodra de timer is geïnitialiseerd en het aftellen is begonnen. De functie wacht niet tot het aftellen is voltooid. Ze retourneert een nummer dat de gebruikte timer identificeert en de uitvoering gaat verder met de volgende instructie, regel 20. Hier wordt het resultaat van [setTimeout] niet gebruikt;
  • regel 16: dit bericht wordt weergegeven aan het einde van de tijdlimiet van 1000 ms van de functie [setTimeout];
  • regels 15-16: de functie f, de eerste parameter van de functie [setTimeout], wordt uitgevoerd aan het einde van de vertraging van 1000 ms. Het bericht van regel 16 wordt dan weergegeven;
  • regel 20: dit bericht wordt weergegeven vóór dat van regel 16;

Tijdfunctie:

  • regel 23: de functie accepteert een optionele parameter [heure], namelijk het starttijdstip van een bewerking waarvan de duur moet worden weergegeven;
  • regels 25-27: de huidige tijd wordt berekend en opgemaakt;
  • regel 29: als de parameter [début] aanwezig is, moet er een duur worden berekend;
  • regel 30: de duur van de bewerking. Dit levert een getal in milliseconden op;
  • regels 31-32: dit aantal milliseconden wordt opgesplitst in seconden en milliseconden;
  • regel 34: de duur wordt bij de tijd opgeteld;

Uitvoering


[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
  • in regel 4 zien we dat de asynchrone actie [setTimeOut] ongeveer 1 seconde na het einde van de hoofdcode van het script is voltooid;
  • regel 6: de tijd die op regel 3 wordt weergegeven, is het tijdstip waarop de hoofdcode is voltooid. Als deze asynchrone taken heeft gestart, is het script pas voltooid wanneer alle asynchrone taken zijn uitgevoerd. De duur die op regel 6 wordt weergegeven, is de totale uitvoeringstijd van het script (hoofdcode + asynchrone taken);

Met de functie [setTimeout] kunnen we asynchrone taken simuleren in een [node.js]-omgeving. De functie [setTimeout] gedraagt zich namelijk als een asynchrone taak:

  • ze levert onmiddellijk een resultaat op, in dit geval een timernummer, via het gebruikelijke mechanisme van functies (return);
  • ze kan later (wat hierboven nog niet het geval is) nog andere resultaten teruggeven via gebeurtenissen die vervolgens worden verwerkt door de gebeurtenislus van [node.js];
  • in de meeste gevallen die hierna volgen, zullen er twee van dergelijke gebeurtenissen zijn:
    • een gebeurtenis die we [success] zouden kunnen noemen, die wordt gegenereerd door de asynchrone taak die haar opdracht met succes heeft voltooid. Aan de gegenereerde gebeurtenis is een gegeven gekoppeld, namelijk het resultaat van de taak;
    • een gebeurtenis die we [failure] zouden kunnen noemen, die wordt gegenereerd door de asynchrone taak die er niet in is geslaagd te doen wat ze moest doen. Aan de gegenereerde gebeurtenis is een gegeven gekoppeld, meestal een object dat de fout beschrijft. Mogelijke fouten bij bijvoorbeeld een asynchrone internettaak zijn ‘netwerk niet beschikbaar’, ‘server bestaat niet’, ‘time-out overschreden’, ...
  • De hoofdcode die een asynchrone taak heeft gestart, kan zich abonneren op de gebeurtenissen die deze taak mogelijk uitzendt. Wanneer een van deze gebeurtenissen wordt uitgezonden, wordt de hoofdcode hiervan op de hoogte gesteld en kan deze de uitvoering van een specifieke functie activeren die bedoeld is om de gebeurtenis af te handelen. Deze functie ontvangt als parameter de gegevens die de asynchrone taak aan de uitgezonden gebeurtenis heeft gekoppeld;

11.2. script [async-02]

In dit script zal de asynchrone functie [setTimeout] gebeurtenissen verzenden om gegevens door te geven aan de codes die zich hierop hebben geabonneerd.

Voor toegang tot de gebeurtenissen van [node.js] zijn extra bibliotheken nodig. We kiezen voor de bibliotheek [events], die we samen met [npm] installeren:

Image

Het script [async-02] is als volgt:


''use strict';

// asynchrone functies kunnen een resultaat retourneren door een gebeurtenis te genereren
// de hoofdcode kan deze resultaten ophalen door zich te abonneren op de verzonden gebeurtenissen

// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';

// begin
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// een gebeurtenisverzender
const eventEmitter = new EventEmitter();

// setTimeout stelt een timer in van 1000 ms (2e parameter) en retourneert onmiddellijk het nummer van deze timer
// wanneer de timer de 1000 ms heeft verstreken, zendt hij een gebeurtenis uit die in de wachtrij van de runtime wordt geplaatst
// wanneer de gebeurtenis door de runtime wordt verwerkt, wordt de functie (eerste parameter) uitgevoerd
setTimeout(function () {
  // deze code wordt uitgevoerd wanneer de timer de waarde 0 heeft bereikt
  console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
  // er wordt een gebeurtenis verzonden om aan te geven dat er een resultaat beschikbaar is
  eventEmitter.emit("timer1Success", { success: 4 });
  // er wordt een andere gebeurtenis verzonden om aan te geven dat er nog een resultaat beschikbaar is
  eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)

// er wordt een abonnement genomen op de gebeurtenis [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)));
});

// er wordt een abonnement genomen op de gebeurtenis [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)));
});

// wordt weergegeven vóór de berichten van de gebeurtenissen die worden verzonden door de functie die is gekoppeld aan [timer1]
console.log("[fin du code principal du script],", heure(débutScript));

// hulpprogramma voor het weergeven van tijd en duur
function heure(début) {
  // huidige tijd
  const now = moment(Date.now());
  // tijdopmaak
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  // moet er een duur worden berekend?
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // opmaak van tijd + duur
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // resultaat
  return result;
}

Opmerkingen

  • regel 9: we importeren de klasse [EventEmitter] uit de bibliotheek [events]. Dit is nieuw: tot nu toe hadden we alleen letterlijke objecten en functies geïmporteerd;
  • regel 15: we maken een gebeurtenisuitzender [node.js] aan door de klasse [EventEmitter] te instantiëren met het sleutelwoord [new];
  • regels 20-27: de asynchrone functie [setTimeout]. Deze zal tijdens de uitvoering twee gebeurtenissen genereren:
    • regel 24: de gebeurtenis [timer1Success] met als bijbehorende waarde het object {success : 4};
    • regel 26: de gebeurtenis [timer1Failure] met als bijbehorende waarde het object {failure: 6};
    • een asynchrone functie kan zoveel gebeurtenissen genereren als ze wil. Eerder werd gezegd dat ze meestal één van de twee gebeurtenissen [success, failure] genereert, niet beide zoals hier het geval is;
  • regel 20: de uitvoering van [setTimeout] gebeurt onmiddellijk: er wordt een timer gestart en het nummer daarvan wordt doorgegeven aan de aanroepende code. Het verzenden van de gebeurtenissen vindt later plaats, in dit geval 1 seconde later;
  • het verzenden van gebeurtenissen heeft geen zin als er geen code is om ze te verwerken wanneer ze zich voordoen. Daarom moet de hoofdcode zich abonneren op beide gebeurtenissen [timer1Success, timer1Failure] als deze ze wil verwerken, met name om de gegevens op te halen die bij deze gebeurtenissen horen;
  • regels 30-32: de hoofdcode abonneert zich op de gebeurtenis [timer1Success]. Wanneer de gebeurtenisbron van [node.js] deze gebeurtenis verwerkt, zal hij de functie aanroepen die de tweede parameter is van de methode [eventEmitter.on], waarbij hij de gegevens (hier [result] genoemd) doorgeeft die bij de gebeurtenis [timer1Success] horen;
  • regel 31: de functie voor de verwerking van de gebeurtenis geeft de jSON van de aan de gebeurtenis gekoppelde gegevens weer, evenals het huidige tijdstip;
  • regels 35-37: met een vergelijkbare code abonneert de hoofdcode zich op de gebeurtenis [timer1Failure];
  • het abonneren op een gebeurtenis (1e parameter) leidt niet onmiddellijk tot de uitvoering van de code van de functie [callback] (2e parameter). Deze wordt pas uitgevoerd nadat de gebeurtenis heeft plaatsgevonden;
  • regel 40: de hoofdcode van het script is voltooid, maar het script zelf nog niet, aangezien de hoofdcode een asynchrone taak heeft gestart. Het totale script is pas voltooid nadat deze asynchrone taak is afgerond;

Dit blijkt uit de verkregen resultaten:


[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

  • regel 3: einde van de hoofdcode 10 ms na het begin van het script;
  • regel 4: begin van de functie die is ingekapseld in de timer van 1000 ms, ongeveer 1 seconde na het begin van het script;
  • regel 5: verwerking van de gebeurtenis [‘timer1Success’], 2 ms later;
  • regel 6: verwerking van de gebeurtenis [‘timer1Failure’], 1 ms later dan de gebeurtenis [‘timer1Success’];
  • regel 8: einde van het globale script met een totale duur van 1,627 seconde;

11.3. script [async-03]

Het volgende script toont een ander aspect van de gebeurtenislus van [node.js]:

  • de lus voert de gebeurtenissen één voor één uit, doorgaans in de volgorde waarin ze binnenkomen. Sommige OS geven prioriteit aan gebeurtenissen, die dan op volgorde van prioriteit worden verwerkt en niet op volgorde van binnenkomst;
  • de lus voert slechts één gebeurtenis tegelijk uit. De volgende wordt pas verwerkt wanneer de verwerking van de vorige is voltooid. In een gebeurtenissysteem moet men daarom vermijden code te schrijven die de processor langdurig in beslag neemt, want dan worden de gebeurtenissen niet verwerkt wanneer ze zich voordoen, maar pas later wanneer de gebeurtenislus erbij aankomt. Men krijgt dan een applicatie die weinig ‘reactief’ is;

Het script [async-03] toont een voorbeeld van dit fenomeen:


'use strict';

// asynchrone functies kunnen een resultaat retourneren door een gebeurtenis te genereren
// de hoofdcode kan deze resultaten ophalen door zich te abonneren op de verzonden gebeurtenissen

// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';

// begin
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// een gebeurtenisverzender
const eventEmitter = new EventEmitter();

// setTimeout stelt een timer in van 1000 ms (2e parameter) en retourneert onmiddellijk het nummer van deze timer
// wanneer de timer de 1000 ms heeft verstreken, zendt hij een gebeurtenis uit die in de wachtrij van de runtime wordt geplaatst
// wanneer de gebeurtenis door de runtime wordt verwerkt, wordt de functie (eerste parameter) uitgevoerd
setTimeout(function () {
  // deze code wordt uitgevoerd zodra de timer de waarde 0 bereikt
  console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
  // er wordt een gebeurtenis verzonden om aan te geven dat er een resultaat beschikbaar is
  eventEmitter.emit("timer1Success", { success: 4 });
  // er wordt een andere gebeurtenis verzonden om aan te geven dat er nog een resultaat beschikbaar is
  eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)

// er wordt een abonnement genomen op de gebeurtenis [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)));
});

// er wordt een abonnement genomen op de gebeurtenis [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)));
});

// een enigszins intensieve synchrone code die ervoor zorgde dat de hoofdcode niet kon worden voltooid vóór het einde van [timer1]
for (let i = 0; i < 1000000; i++) {
  for (let j = 0; j < 10000; j++) {
    i + i ^ 2 + i ^ 3;
  }
}

// zal worden weergegeven vóór de berichten van de gebeurtenissen die worden verzonden door de functie die is gekoppeld aan [timer1]
console.log("[fin du script],", heure(débutScript));

// hulpprogramma voor het weergeven van tijd en duur
function heure(début) {
 ...
}

Opmerkingen

  • deze code is afkomstig uit het vorige voorbeeld [async-02], waaraan de regels 39-44 zijn toegevoegd;
  • regels 20-27: de functie [setTimeout] is zo geprogrammeerd dat deze na een vertraging van één seconde een interne asynchrone functie uitvoert. Na deze seconde vindt de uitvoering van de asynchrone timerfunctie niet onmiddellijk plaats: er wordt een gebeurtenis in de uitvoeringslus geplaatst om deze op te roepen. Als de uitvoeringslus bezig is met het verwerken van een andere gebeurtenis, moet de uitvoering van de asynchrone timerfunctie wachten;
  • regels 20-27: zodra de functie [setTimeout] haar timer heeft ingesteld op een vertraging van één seconde, laat ze de processor los en geeft ze de controle terug aan de aanroepende code. Deze gaat verder met de regels 30-37, die bestaan uit abonnementen op gebeurtenissen en waarvan de uitvoeringstijd verwaarloosbaar is;
  • de hoofdcode gaat verder met de regels 40-44, die een lus van 1010 iteraties vormen. Deze code is in uitvoering wanneer de timer zijn gebeurtenis „einde van de vertraging van 1 seconde“ verzendt. Deze gebeurtenis wordt vervolgens in de gebeurtenislus geplaatst, maar moet wachten tot de hoofdcode van het script is uitgevoerd om in aanmerking te komen voor verwerking;
  • regel 47: einde van de hoofdcode van het script. Pas na deze laatste weergave kan de gebeurtenis ‘einde van de timer’ worden verwerkt en kan de interne asynchrone functie van [setTimeout] worden uitgevoerd;

Het script levert de volgende resultaten op:


[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

Opmerkingen

  • regel 3: we zien dat de uitvoering van de hoofdcode van het script 9 seconden heeft geduurd. De gebeurtenissen die zich in die tijd konden voordoen, zijn in de gebeurtenislus in de wachtrij geplaatst;
  • regel 4: we zien dat de gebeurtenis [fin du timer] 5 ms na het einde van de hoofdcode is verwerkt. Deze gebeurtenis werd ongeveer 1 s na het begin van het script gegenereerd, maar moest nog 8 s wachten voordat ze uiteindelijk werd verwerkt;

Wat we uit dit voorbeeld kunnen leren, is dat in een gebeurtenisgestuurd systeem code de processor nooit te lang mag bezetten. Als we een synchrone code hebben die lang duurt om uit te voeren, moeten we deze ‘op een of andere manier’ opsplitsen in kortere asynchrone taken die hun voltooiing met een gebeurtenis signaleren.

11.4. script [async-04]

Het script [async-04] toont een ander mechanisme, genaamd [Promise], een resultaatbelofte. Dit mechanisme voorkomt dat [node.js]-gebeurtenissen expliciet moeten worden beheerd. Dit gebeurt impliciet en de ontwikkelaar kan het bestaan van deze gebeurtenissen dan negeren. Als hij deze echter begrijpt, zal hij de werking van de [Promise] beter kunnen doorgronden, wat op het eerste gezicht complex lijkt.

Het type [Promise] is een JavaScript-klasse. De constructor ervan accepteert als parameter een asynchrone functie waaraan hij twee parameters doorgeeft die traditioneel [resolve] en [reject] worden genoemd. Ze zouden ook een andere naam kunnen hebben;

1
2
3
4
5
6
7
const promise=new Promise(function(resolve, reject){
     // er wordt een asynchrone taak gestart
    
     // bij succes: roep resolve(result) aan, waarbij [result] het resultaat is van de asynchrone taak;
     // indien mislukt: roep reject(error) aan, waarbij [error] een object is dat de opgetreden fout bevat;
}
// abonneer je op de gebeurtenissen die worden gegenereerd door de asynchrone taak van [Promise]
  • de constructor van [Promise] doet twee dingen:
    • hij creëert een gebeurtenis om de uitvoering van de functie [function(resolve, reject)], die als parameter is doorgegeven, te starten, maar wacht niet op het resultaat en retourneert onmiddellijk een object [Promise] aan de aanroepende code. Dit object kan vier statussen hebben:
      • [pending]: de asynchrone actie die [Promise] heeft gegenereerd, is nog niet voltooid;
      • [fulfilled]: de asynchrone actie die [Promise] heeft gegenereerd, is succesvol voltooid;
      • [rejected]: de asynchrone actie die [Promise] heeft gegenereerd, is mislukt;
      • [settled]: de asynchrone actie die [Promise] heeft geretourneerd, is voltooid;

Wanneer de constructor het resultaat retourneert, bevindt het aangemaakte object [Promise] zich in de status [pending], in afwachting van de resultaten van de asynchrone functie;

  • (vervolg)
    • de asynchrone taak van de regels 2-5 wordt onmiddellijk gestart. Asynchrone taken zijn meestal asynchrone invoer-/uitvoertaken die als volgt zijn opgebouwd:
      1. uitvoering van synchrone code om de I/O-bewerking met een ander onderdeel te starten, bijvoorbeeld een externe server;
      2. wachten op het antwoord van dat apparaat;
      3. verwerking van dit antwoord;

Fase 2, het wachten op de externe component, is het meest rekenintensief. In plaats van te wachten:

  • zal de ontvangst van de opgevraagde gegevens van het externe apparaat worden gesignaleerd door een gebeurtenis;
  • in de synchrone code die volgt op fase 1 (regel 7 van de voorbeeldcode), wordt men geabonneerd op deze gebeurtenis en keert men vervolgens op een bepaald moment terug naar de gebeurtenislus van [node.js]. De volgende gebeurtenis in de lijst met wachtende gebeurtenissen wordt dan verwerkt;
  • tijdens fase 2 vindt er parallelle uitvoering plaats, maar op verschillende apparaten:
    • de processor voor de gebeurtenissenlus;
    • een extern apparaat (schijf, database, externe server) voor het opzoeken van de gevraagde gegevens;
  • aan het einde van fase 2, wanneer de I/O-bewerking de gevraagde gegevens heeft verkregen, wordt er een gebeurtenis gegenereerd om aan te geven dat het resultaat van de I/O beschikbaar is. Deze gebeurtenis voegt zich vervolgens bij de andere gebeurtenissen in de wachtrij;
  • wanneer het aan de beurt is, wordt het verwerkt. De functie die aan deze gebeurtenis is gekoppeld (regel 7 van de voorbeeldcode) wordt dan uitgevoerd;

Deze werkwijze voorkomt wachttijden: de situatie waarin de processor moet wachten op het antwoord van een apparaat dat langzamer is dan hijzelf;

  • (vervolg)
    • wanneer de asynchrone taak van de regels 2 en 5 is gestart en haar werk heeft voltooid, kan zij een resultaat teruggeven aan de aanroepende code, dankzij de twee functies [resolve, reject] die de constructor [Promise] als parameters aan haar heeft doorgegeven. De conventie is als volgt:
      • de asynchrone taak meldt succes via [resolve(result)]. Dit komt neer op het toevoegen aan de gebeurtenissenlus van [node.js] van een gebeurtenis die we [resolved] zouden kunnen noemen, met [result] als bijbehorende gegevens;
      • de asynchrone taak meldt een mislukking via [reject(error)]. Dit komt neer op het plaatsen in de gebeurtenissenlus van [node.js] van een gebeurtenis die we [rejected] zouden kunnen noemen, met [error] als bijbehorende gegevens, doorgaans een object dat de opgetreden fout beschrijft;
      • de aanroepende code moet zich dus op deze twee gebeurtenissen abonneren om op de hoogte te worden gesteld van de beschikbaarheid van het resultaat van de asynchrone functie;

Nadat de uitvoering van de asynchrone taak die is ingekapseld in [Promise] is voltooid, verandert de status van het object [promise] dat door de constructor [Promise(…)] is geretourneerd:

  • de gebeurtenis [resolved] zorgt ervoor dat deze van de status [pending] naar [resolved] gaat;
  • de gebeurtenis [rejected] zorgt ervoor dat het van de status [pending] naar [rejected] gaat;

Het abonneren op de gebeurtenissen [resolved] en [rejected] van de asynchrone taak gebeurt met methoden van de klasse [Promise] met de volgende syntaxis:

promise.then(f1).catch(f2).finally(f3);

waarbij:

  • f1 is een functie die wordt uitgevoerd wanneer de status van [promise] verandert van [pending] naar [resolved], dus wanneer de asynchrone taak zijn taak met succes heeft voltooid. Deze functie ontvangt als parameter de waarde [result], die wordt doorgegeven door de instructie [resolve(result)] van de asynchrone taak;
  • f2 is een functie die wordt uitgevoerd wanneer de status van [promise] verandert van [pending] naar [rejected], dus wanneer de asynchrone taak er niet in is geslaagd haar taak uit te voeren. Deze functie ontvangt als parameter de waarde [error], doorgegeven door de instructie [reject(errror)] van de asynchrone taak;
  • f3 is een functie die wordt uitgevoerd na de uitvoering van de methoden [then] of [catch], en wordt dus altijd uitgevoerd. Ze ontvangt geen parameters;

Deze syntaxis verbergt volledig de gebeurtenissen waarop men zich abonneert. Het gaat echter wel degelijk om een abonnement en net als in het vorige voorbeeld worden de functies [f1, f2, f3] niet onmiddellijk uitgevoerd. Deze zullen al dan niet worden uitgevoerd wanneer een van de gebeurtenissen [resolved, rejected] waarop men zich abonneert, zich voordoet.

Het script [async-04] illustreert dit mechanisme:


'use strict';

// het is mogelijk om de resultaten (succes, mislukking) van een asynchrone functie op te halen
// zonder expliciet gebruik te maken van gebeurtenissen, dankzij de klasse [Promise]
// deze klasse maakt impliciet gebruik van gebeurtenissen, maar deze zijn niet zichtbaar in de code

// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';

// begin
const débutScript = moment(Date.now());
console.log("[début du script],", heure(débutScript));

// definitie van een asynchrone taak met behulp van een promise [Promise]
// de asynchrone taak is de parameter van de constructor [Promise]
const débutPromise1 = moment(Date.now());
const promise1 = new Promise(function (resolve) {
  // log
  console.log("[début fonction asynchrone de promise1],", heure(débutPromise1));
  // asynchrone code
  setTimeout(function () {
    console.log("[fin fonction asynchrone de promise1],", heure(débutPromise1));
    // de asynchrone taak retourneert een resultaat met de functie [resolve]
    // de belofte is dan geslaagd
    resolve('[réussite]');
  }, 1000)
});

// het resultaat van de belofte kan worden achterhaald met de functie [promise1]
// wanneer deze is opgelost (resolve) of afgewezen (reject)
// de volgende instructie is een abonnement op de gebeurtenis [resolved] via de methode [then]
// en op de gebeurtenis [rejected] via de methode [catch]
// de methode [finally] wordt uitgevoerd, of dit nu na een 'then' of een 'catch' is
promise1.then(result => {
  // in het geval dat de belofte slaagt  [evt resolved]
  console.log(sprintf("[promise1.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
  // in geval van een fout  [evt rejected]
  console.log(sprintf("[promise1.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
  // in alle gevallen uitgevoerd
  console.log("[promise1.finally]", heure(débutPromise1));
});

// definitie van een asynchrone taak met behulp van een 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));
  // asynchrone taak
  setTimeout(function () {
    console.log("[fin fonction asynchrone de promise2],", heure(débutPromise2));
    // de asynchrone taak retourneert een resultaat met de functie [reject]
    // de belofte is dan mislukt
    reject('[échec]');
  }, 2000)
});

// het resultaat van de belofte kan worden achterhaald met [promise2]
// wanneer deze is opgelost (resolve) of afgewezen (reject)
promise2.then(result => {
  // in het geval dat de belofte is geslaagd [evt resolved]
  console.log(sprintf("[promise2.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
  // in geval van een fout [evt rejected]
  console.log(sprintf("[promise2.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
  // wordt in alle gevallen uitgevoerd
  console.log(sprintf("[promise2.finally], %s", heure(débutPromise2)));
});

// wordt weergegeven vóór de berichten van de asynchrone functies en die van de bijbehorende gebeurtenissen
console.log("[fin du code principal du script],", heure(débutScript));

// hulpprogramma
function heure(début) {
  // huidige tijd
  const now = moment(Date.now());
  // tijdopmaak
  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);
    // opmaak van de duur
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // resultaat
  return result;
}

Opmerkingen

  • regels 18-28: aanmaken van een [Promise promise1]. De asynchrone functie ervan levert het resultaat na één seconde via een gebeurtenis. Zodra deze asynchrone bewerking is gestart (het starten van een timer), wordt niet gewacht tot het resultaat wordt geleverd, maar wordt onmiddellijk doorgegaan naar de code op regel 35;
  • regels 35-44: we abonneren ons op de twee gebeurtenissen [resolved, rejected] die de interne asynchrone functie van [promise1] kan genereren;
  • regels 46-71: we herhalen dezelfde codereeks als eerder voor een tweede belofte [promise2];
  • regel 74: de hoofdcode van het script is voltooid, maar het script als geheel nog niet, omdat er twee asynchrone acties zijn gestart. We keren terug naar de gebeurtenislus, waar op een gegeven moment een van de gebeurtenissen [resolved, rejected] van de beloften [promise1, promise2] zal plaatsvinden. Deze wordt dan verwerkt;
  • daarna keert men terug naar de gebeurtenislus. En daar zal de tweede gebeurtenis [resolved, rejected] van de beloften [promise1, promise2] worden verwerkt wanneer deze zich voordoet;

Uitvoering


[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

Opmerkingen

  • regel 3: de asynchrone functie van [promise1] wordt gestart, maar er wordt niet gewacht op de voltooiing ervan, die door een gebeurtenis zal worden gemeld;
  • regel 4: de asynchrone functie van [promise2] wordt gestart, maar er wordt niet gewacht tot deze is voltooid, wat door een gebeurtenis zal worden gemeld;
  • regel 5: einde van de hoofdcode en terugkeer naar de gebeurtenislus;
  • regel 6: verwerking van de gebeurtenis [fin fonction asynchrone de promise1]. De status van [promise1] verandert in [resolved]. Dit wordt door een gebeurtenis gemeld;
  • regel 7: aangezien [promise2] zijn taak nog niet heeft voltooid, zal de gebeurtenis [promise1 resolved], die zojuist in de lus is geplaatst, worden verwerkt door de methode [promise1.then] en vervolgens door de methode [promise.finally] (regel 8);
  • regels 9-11: hetzelfde mechanisme vindt plaats wanneer [promise2] van de status [pending] naar [resolved] overgaat;

11.5. script [async-05]

Laten we teruggaan naar de code van de constructor van een object [Promise]:

1
2
3
4
5
6
7
8
9
'use strict'; 

const promise=new Promise(function(resolve, reject){
// er wordt een asynchrone taak gestart
// …
// bij succes: roep resolve(result) aan, waarbij [result] het resultaat is van de asynchrone taak;
// bij mislukking: roep reject(error) aan, waarbij [error] een object is dat de opgetreden fout bevat;
}
// abonneren op de gebeurtenissen die door de asynchrone taak worden gegenereerd

Op regel 2 wordt de asynchrone taak van [Promise] gestart. Deze taak heeft vaak meer parameters nodig dan alleen de [resolve, reject]-parameters die de functie die deze taak omvat, doorgeeft. In dit geval wordt het aanmaken van de [Promise] ingekapseld in een functie die de parameters doorgeeft die de asynchrone taak nodig heeft:

'use strict';

// definitie van de asynchrone functie
function uneFonctionAsynchrone (p1, p2, , pn){
 return new Promise(function(resolve, reject){
     // er wordt een asynchrone taak gestart met de parameters (P1, p2, …, pn)
    // …
     // bij succes: roep resolve(result) aan, waarbij [result] het resultaat van de asynchrone taak is;
     // bij mislukking: roep reject(error) aan, waarbij [error] een object is dat de opgetreden fout bevat;
}
// weabonneren op de gebeurtenissen [resolved, rejected] die de asynchrone functie [uneFonctionAsynchrone] zal genereren

// Even later wordt de asynchrone functie [uneFonctionAsynchrone] aangeroepen
uneFonctionAsynchrone(e1, e2, , en) ;

Het volgende script:

  • definieert twee asynchrone functies die een [Promise] retourneren;
  • start de uitvoering ervan parallel op en wacht tot beide zijn voltooid om een bepaalde taak uit te voeren;

'use strict';

// Er kunnen asynchrone functies worden gedefinieerd die een type [Promise] retourneren
// en ze kunnen vervolgens worden gemarkeerd met het trefwoord [async]

// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';

// begin
const débutScript = moment(Date.now());
console.log("[début du script],", heure());

// een asynchrone functie die een belofte retourneert [Promise]
function async01(p1) {
  return new Promise((resolve) => {
    console.log("[début de la tâche asynchrone async01]");
    // de asynchrone taak
    const débutAsync01 = moment(Date.now());
    setTimeout(function () {
      console.log("[fin de la tâche asynchrone async01],", heure(débutAsync01));
      // de asynchrone taak kan een complex resultaat opleveren
      resolve({
        prop1: [10, 20, 30],
        prop2: "abcd",
        prop3: p1,
      });
    }, 1000)
  });
}

// een asynchrone functie die een belofte retourneert [Promise]
function async02(p1, p2) {
  return new Promise(resolve => {
    console.log("[début de la tâche asynchrone async02]");
    // asynchrone taak
    const débutAsync02 = moment(Date.now());
    setTimeout(function () {
      console.log("[fin de la tâche asynchrone async02],", heure(débutAsync02));
      // de asynchrone taak kan een complex resultaat opleveren
      resolve({
        prop1: [11, 21, 31],
        prop2: "xyzt",
        prop3: p1 + p2
      });
    }, 2000)
  })
}

// we starten beide asynchrone functies parallel
// en we wachten tot ze allebei zijn voltooid
// de 'then' wordt alleen uitgevoerd als beide functies de gebeurtenis [resolved] hebben gegenereerd
// de `catch` wordt uitgevoerd zodra een van de twee functies de gebeurtenis [rejected] heeft gegenereerd
Promise.all([async01(10), async02(10, 20)])
  // het resultaat is een array [result1, result2], waarbij [result1] het resultaat is dat wordt gegenereerd door een [resolve] van [async01]
  // en [result2] het resultaat is dat wordt gegenereerd door een [resolve] van [async02]
  .then(result => {
    console.log(sprintf("[promise-all success], %s, result=%j", heure(débutScript), result));
  })
  // error is het resultaat dat wordt gegenereerd door de eerste [reject] van een van de twee asynchrone functies
  .catch(error => {
    console.log(sprintf("[promise-all error], %s, erreur=%j", heure(débutScript), error));
  })
  // finally wordt uitgevoerd na de then of de catch
  .finally(() => {
    console.log(sprintf("[promise-all finally], %s", heure(débutScript)));
  });

// wordt weergegeven vóór de berichten van de asynchrone functies en de bijbehorende gebeurtenissen
console.log("[fin du code principal du script],", heure(débutScript));

// hulpprogramma
function heure(début) {
  // huidige tijd
  const now = moment(Date.now());
  // tijdopmaak
  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);
    // opmaak van de duur
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // resultaat
  return result;
}

Opmerkingen

  • regels 15-30: we definiëren een functie [async01] die na 1 seconde haar resultaat retourneert via een timergebeurtenis. De functie [async01] wordt gebruikt in het resultaat op regel 26;
  • regels 33-47: we doen hetzelfde met een functie [async02] die haar resultaat na 2 seconden via een timergebeurtenis retourneert. De functie [async02] heeft twee parameters die in haar resultaat op regel 44 worden gebruikt;
  • wanneer de twee functies [async01, async02] worden aangeroepen:
    • gestart;
    • leveren ze twee beloften [promise1, promise2] aan de aanroepende code;
    • de uitvoering keert dan terug naar de aanroepende code, die verdergaat;
    • na ongeveer 1 seconde zal [async01] een gebeurtenis genereren om aan te geven dat het zijn taak heeft voltooid. De betreffende gebeurtenis wordt in de wachtrij geplaatst van de gebeurtenislus die gekoppeld is aan het door [async01] doorgegeven resultaat met de gebeurtenis;
    • na ongeveer 2 seconden zal hetzelfde proces plaatsvinden voor [async02];
  • regel 54: pas nu worden de asynchrone functies [async01, async02] uitgevoerd (notaties async01(10) en async02(10,20)). Dit gebeurt binnen een array die als parameter wordt doorgegeven aan de methode [Promise.all]. We weten dat [async01, async02] beide een belofte teruggeven aan de aanroepende code. De parameter van [Promise.all] is dus een array van twee beloften;
  • [Promise.all([promise1, promise2, …, promisen]).then(f1).catch(f2).finally(f3)] is een abonnement op gebeurtenissen:
    • [Promise.all] is van het type [Promise];
    • de functie [f1] van de methode [then] wordt uitgevoerd wanneer alle beloften [promise1, promise2, …, promisen] uit de parameterarray van de methode [all] zijn overgegaan van destatus [pending] naar de status [resolved] zijn overgegaan. Met andere woorden: [f1] wordt uitgevoerd wanneer alle beloften in de array met succes zijn voltooid;
    • de functie [f2] van de methode [catch] wordt uitgevoerd zodra een van de beloften in de array van de status [pending] naar de status [rejected] overgaat. Met andere woorden: [f2] wordt uitgevoerd zodra een van de beloften in de array mislukt;
    • de functie [f3] van de methode [finally] wordt uitgevoerd na de uitvoering van een van de methoden [then, catch], en wordt dus altijd uitgevoerd;

De uitvoering van de code levert de volgende resultaten op:


[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
  • regels 6-7: de twee asynchrone taken [async01, async02] worden gestart. Ze werken parallel. Niet de uitvoering van hun code gebeurt parallel, maar hun respectieve wachttijden op de gevraagde gegevens vinden tegelijkertijd plaats;
  • regel 5: de hoofdcode van het script is voltooid. Nu moet nog worden gewacht tot de twee asynchrone taken [async01, async02] zijn voltooid;
  • regel 6: de asynchrone taak [async01] wordt ongeveer 1 s na het starten voltooid. Deze levert een resultaat op via de functie [resolve], waardoor de belofte in de tabel op regel 56 van de code van de status [pending] naar [resolved] verandert. Dit is niet voldoende om de methode [then] te activeren, regels 59-60 van de code;
  • regel 7: de asynchrone taak [async02] wordt ongeveer 2 s na het starten voltooid. Deze taak retourneert een resultaat via de functie [resolve], waardoor de belofte in de tabel op regel 56 van de code van de status [pending] naar [resolved] verandert. De methode [then] wordt uitgevoerd zodra de gebeurtenislus dit toestaat;
  • regel 8: de methode [then] van [Promise.all] wordt uitgevoerd. Deze ontvangt als parameter een array [result1, result2], waarbij [result1] het resultaat is dat door [async01] is gegenereerd, en [result2] het resultaat is dat door [async02] is gegenereerd;
  • regel 9: de methode [finally] van [Promise.all] wordt uitgevoerd;

11.6. script [async-06]

Dit nieuwe script laat zien hoe het gecombineerde gebruik van de sleutelwoorden [async / await] het mogelijk maakt om asynchrone code te schrijven die lijkt op synchrone code. De afhandeling van gebeurtenissen is volledig verborgen, waardoor de code gemakkelijker te begrijpen is.

We nemen het vorige voorbeeld weer op en brengen daarin de volgende wijzigingen aan:

  • we voegen een derde asynchrone functie [async03] toe, die haar resultaat teruggeeft via de methode [Promise.reject], die vervolgens aan de gebeurtenislus meldt dat ze er niet in is geslaagd haar taak uit te voeren;
  • we voeren de drie asynchrone functies [async01, async02, async03] sequentieel uit. In het vorige voorbeeld hadden we de asynchrone functies [async01, async02] parallel uitgevoerd;
  • voordat de sleutelwoorden [async/await] verschenen, gebeurde de sequentiële uitvoering van asynchrone acties met behulp van in elkaar geneste [Promise]-functies. Zodra er op deze manier meerdere asynchrone acties moesten worden uitgevoerd, nam het aantal beloften dienovereenkomstig toe en werd de code minder leesbaar;
  • met de sleutelwoorden [async/await] gebeurt de sequentiële uitvoering van asynchrone taken met een syntaxis die overeenkomt met die van de uitvoering van synchrone taken:

// asynchrone functie - gebruik van async/await
async function main() {
  // sequentiële uitvoering van asynchrone taken
  try {
    // uitvoering met wachten op [async01]
    const result1 = await async01(...);
    console.log("[async01 result]=", result1);
    // uitvoering met wachten op [async02]
    const result2 = await async02(...);
    console.log("[async02 result]=", result2);
    // uitvoering in afwachting van [async03]
    const result3 = await async03(...);
    console.log("[async03 result]=", result3);
  } catch (error) {
    // een van de asynchrone acties is mislukt
    console.log(sprintf("[sequential error]= %j, %s", error));
  } finally {
    // voltooid
    console.log("[fin exécution séquentielle des tâches asynchrones],");
  }
  • regel 6: de asynchrone functie [async01] wordt gestart (trefwoord await) en er wordt gewacht totdat deze het resultaat heeft gepubliceerd via een van de methoden [Promise.resolve, Promise.reject]. Dit is dus een blokkerende bewerking;
  • regel 6: het trefwoord [await] zet de asynchrone bewerking [async01] om in een blokkerende bewerking. We weten dat de bewerking [async01] op twee manieren een resultaat retourneert:
    • het levert vrijwel onmiddellijk een object [Promise] af aan de aanroepende code;
    • vervolgens publiceert het een resultaat via de methoden [Promise.resolve, Promise.reject] in de gebeurtenislus. Het is dit laatste resultaat dat door [result1], regel 6, wordt opgehaald. De gebeurtenisafhandeling van de actie [async01] is onzichtbaar geworden;
    • als het resultaat [result] van [async01] wordt gepubliceerd door [Promise.resolve(result)], wordt het toegewezen aan [result1], regel 6, en gaat de uitvoering verder op regel 7;
    • Als het resultaat van [async01] wordt gepubliceerd door [Promise.reject], leidt dit tot een uitzondering en gaat de uitvoering van de code verder bij regel 14, de regel van de catch. De parameter van de clausule [catch] is het foutobject (error) dat door [async01] wordt gepubliceerd met een uitdrukking [Promise.reject(error)]. De asynchrone taak kan de fout ook via een [throw(error)] publiceren. Het object [error] is het object dat in [catch(error)] is opgehaald;
    • het sleutelwoord [await] moet verplicht voorkomen in een functie die wordt voorafgegaan door het sleutelwoord [async], regel 2. Dit sleutelwoord geeft aan dat de functie [main] een asynchrone functie is;
    • in de uitdrukking [await f(…)] moet [f] een asynchrone functie zijn die een object [Promise] teruggeeft aan de aanroepende code;
  • we doen hetzelfde voor de asynchrone actie [async02], regel 9, en [async03], regel 12;

Met dezelfde sleutelwoorden [async / await] is het mogelijk om asynchrone taken parallel uit te voeren met de volgende syntaxis:


try {
    // parallelle uitvoering van asynchrone taken
    const result = await Promise.all([async01(...), async02(...), async03(...)]);
    console.log(sprintf("[parallel success], %s, result=%j", heure(débutParallel), result));
  } catch (error) {
    // een van de asynchrone acties is mislukt
    console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
  } finally {
    // voltooid
    console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
}
  • regel 3: hier is sprake van een blokkerende bewerking: er wordt gewacht totdat de drie asynchrone taken uit de array [async01(..), async02(..), async03(..)] hun resultaten op de gebeurtenislus hebben gepubliceerd met een van de methoden [Promise.resolve, Promise.reject];
  • als de drie asynchrone taken hun resultaten publiceren met [Promise.resolve], dan is de constante [result] de array [result1, result2, result3], waarbij:
    • [result1] het resultaat is dat door [async01] is gepubliceerd met de uitdrukking [Promise.resolve(result1)];
    • [result2] is het resultaat dat door [async02] is gepubliceerd met de uitdrukking [Promise.resolve(result2)];
    • [result3] is het resultaat dat door [async03] is gepubliceerd met de uitdrukking [Promise.resolve(result3)];
  • als een van de drie taken zijn resultaat publiceert met de uitdrukking [Promise.reject(error)], dan treedt er een uitzondering op;
    • de constante [result] op regel 3 krijgt geen waarde;
    • de uitvoering gaat direct verder met [catch] op regel 5;
    • de parameter (error) van de catch is het object (error) dat door de uitdrukking [Promise.reject(error)] wordt gepubliceerd;

Door deze twee syntaxisvormen te combineren, kunnen asynchrone taken zowel sequentieel als parallel worden uitgevoerd, en dat alles met een syntaxis die vergelijkbaar is met die van synchrone code. Deze syntaxis is veel leesbaarder dan de voorgaande en verdient daarom de voorkeur. Deze syntaxis [async / await] is pas beschikbaar vanaf versie 6 van ECMAScript. Er is nog steeds veel JavaScript-code die gebruikmaakt van [Promise]-promises. Daarom is het belangrijk om ook de werking daarvan te begrijpen.

De volledige code van het script [async-06] is als volgt:


''use strict';

// parallelle of sequentiële uitvoering van meerdere asynchrone taken
// met de sleutelwoorden async / await

// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';

// begin
const débutScript = moment(Date.now());
console.log("[début du code principal du script],", heure());

// een asynchrone functie die een [Promise] retourneert
function async01(débutAsync01) {
  return new Promise(function (resolve) {
    console.log("[début fonction asynchrone async01],", heure());
    // asynchrone functie
    setTimeout(function () {
      console.log("[fin fonction asynchrone async01],", heure(débutAsync01));
      // de asynchrone actie kan een complex resultaat opleveren
      // hier geslaagd
      resolve({
        prop1: [11, 21, 31],
        prop2: "abcd"
      });
    }, 1000)
  });
}

// een asynchrone functie die een [Promise] retourneert
function async02(débutAsync02) {
  console.log("[début fonction asynchrone async02],", heure());
  return new Promise(function (resolve) {
    // asynchrone functie
    setTimeout(function () {
      console.log("[fin fonction asynchrone async02],", heure(débutAsync02));
      // de asynchrone actie kan een complex resultaat opleveren
      // hier geslaagd
      resolve({
        prop1: [12, 22, 32],
        prop2: "xyzt"
      });
    }, 2000)
  })
}

// een asynchrone functie die een [Promise] retourneert
function async03(débutAsync03) {
  console.log("[début fonction asynchrone async03],", heure());
  return new Promise((resolve, reject) => {
    // asynchrone functie
    setTimeout(function () {
      console.log("[fin fonction asynchrone async03],", heure(débutAsync03));
      // de asynchrone actie kan een complex resultaat opleveren
      // hier mislukt
      reject({
        prop1: [13, 23, 33],
        prop2: "échec"
      });
    }, 3000)
  })
}

// asynchrone functie – gebruik van async/await
async function main() {
  const débutSequential = moment(Date.now());
  // sequentiële uitvoering van asynchrone taken
  console.log("------------ exécution séquentielle des tâches asynchrones lancée ------------------------")
  try {
    // uitvoering met wachten op [async01]
    const débutAsync01 = moment(Date.now());
    const result1 = await async01(débutAsync01);
    console.log("[async01 result]=", result1);
    // uitvoering met wachten op [async02]
    const débutAsync02 = moment(Date.now());
    console.log("début async02-------------", heure());
    const result2 = await async02(débutAsync02);
    console.log("[async02 result]=", result2);
    // uitvoering in afwachting van [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) {
    // een van de asynchrone acties is mislukt
    console.log(sprintf("[sequential error]= %j, %s", error, heure(débutSequential)));
  } finally {
    // voltooid
    console.log("[fin exécution séquentielle des tâches asynchrones],", heure(débutSequential));
  }

  const débutParallel = moment(Date.now());
  // parallelle uitvoering van asynchrone taken
  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) {
    // een van de asynchrone acties is mislukt
    console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
  } finally {
    // voltooid
    console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
  }

  // voltooid
  console.log("[fin de la fonction main],", heure(débutSequential));
}
// uitvoering van de asynchrone functie main
main();

// wordt weergegeven vóór de verschillende berichten van de asynchrone functies en hun gebeurtenissen
console.log("[fin du code principal du script],", heure(débutScript));

// hulpprogramma
function heure(début) {
  // huidige tijd
  const now = moment(Date.now());
  // tijdopmaak
  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);
    // opmaak van de duur
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // resultaat
  return result;
}

De resultaten van de uitvoering zijn als volgt:


[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