Skip to content

11. Programmazione event-driven e funzioni asincrone

Image

Una funzione asincrona è una funzione la cui esecuzione viene avviata ma di cui non si attende il risultato. Al termine dell’esecuzione, la funzione asincrona genera un evento e trasmette il proprio risultato tramite esso.

Questa modalità di funzionamento è particolarmente adatta all’esecuzione all’interno di un browser web. Infatti, un’applicazione eseguita all’interno di un browser è un’applicazione basata sugli eventi: l’applicazione reagisce agli eventi, provocati principalmente dall’utente (clic, movimenti del mouse, digitazione di testo, ecc.). Le applicazioni JavaScript eseguite all’interno di un browser sono chiamate a interagire con servizi esterni tramite il protocollo HTTP. Le funzioni native HTTP di JavaScript sono asincrone: vengono avviate e il ricevimento della risposta dal servizio esterno richiesto viene segnalato da un evento che si aggiunge all’insieme degli eventi gestiti dall’applicazione.

I seguenti script verranno eseguiti da [node.js] e non da un browser. Anche [node.js] ha una modalità di esecuzione basata sugli eventi:

  • [node.js], come un browser, utilizza un ciclo di eventi per eseguire uno script;
  • l’esecuzione del codice principale dello script è il primo evento eseguito;
  • se questo codice principale ha avviato attività asincrone, l’esecuzione prosegue finché tali attività non sono terminate. Queste generano un evento al loro completamento. Tali eventi vengono messi in coda nel ciclo di eventi;
  • lo script principale deve sottoscriversi a questi eventi se desidera recuperare i risultati delle azioni asincrone;
  • lo script è terminato solo quando tutti gli eventi da esso generati sono stati elaborati;

11.1. script [async-01]

Lo script seguente illustra il comportamento di uno script che include un’azione asincrona.


'use strict';

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

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

// setTimeout avvia un timer di 1000 ms (secondo parametro) e restituisce immediatamente il numero di tale timer
// quando il timer ha esaurito i 1000 ms, emette un evento che viene messo in coda dal runtime
// quando l'evento viene elaborato dal runtime, viene eseguita la funzione (primo parametro)
setTimeout(function () {
  // questo codice verrà eseguito quando il timer avrà raggiunto il valore 0
  console.log("[fin de l'action asynchrone setTimeout],", heure(débutScript));
}, 1000)

// verrà visualizzato prima del messaggio della funzione interna al timer
console.log("[fin du code principal du script],", heure(débutScript));

// utilità per la visualizzazione dell'ora e della durata
function heure(début) {
  // ora corrente
  const now = moment(Date.now());
  // formattazione dell'ora
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  // è necessario calcolare una durata?
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // formattazione dell'ora + durata
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // risultato
  return result;
}
  • riga 4: si importa la libreria [moment] per poter formattare le date (riga 27);
  • riga 5: si importa la libreria [sprintf-js] per poter formattare le durate (riga 34);
  • riga 8: si registra l'ora di inizio dello script;
  • riga 9: la si visualizza utilizzando il metodo [heure] delle righe 20-34;
  • righe 14-17: la funzione [setTimeout] ha due parametri (f, durata): f è una funzione che viene eseguita quando sono trascorsi [durée] millisecondi;
  • riga 14: all’esecuzione dello script, viene eseguita la funzione [setTimeout]:
    • riga 17: viene avviato un timer da 1000 ms e inizia il conto alla rovescia fino a raggiungere successivamente lo 0. La funzione [setTimeout] termina non appena il timer viene inizializzato e il conto alla rovescia ha inizio. Non attende la fine di tale conto alla rovescia. Restituisce un numero che identifica il timer utilizzato e l’esecuzione passa all’istruzione successiva, riga 20. Qui, il risultato di [setTimeout] non viene utilizzato;
  • riga 16: questo messaggio verrà visualizzato al termine del tempo di 1000 ms della funzione [setTimeout];
  • righe 15-16: la funzione f, primo parametro della funzione [setTimeout], verrà eseguita al termine del tempo di 1000 ms. Verrà quindi visualizzato il messaggio della riga 16;
  • riga 20: questo messaggio verrà visualizzato prima di quello della riga 16;

Funzione ora:

  • riga 23: la funzione accetta un parametro facoltativo [heure] che rappresenta l'ora di inizio di un'operazione di cui deve visualizzare la durata;
  • righe 25-27: si calcola e si formatta l’ora corrente;
  • riga 29: se è presente il parametro [début], occorre calcolare una durata;
  • riga 30: la durata dell’operazione. Si ottiene un numero di millisecondi;
  • righe 31-32: questo numero di millisecondi viene scomposto in secondi e millisecondi;
  • riga 34: la durata viene aggiunta all'ora;

Esecuzione


[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
  • alla riga 4, si vede che l'azione asincrona [setTimeOut] si è conclusa circa 1 secondo dopo la fine del codice principale dello script;
  • riga 6: l’ora visualizzata alla riga 3 è quella della fine del codice principale. Se quest’ultimo ha avviato attività asincrone, lo script termina solo quando tutte le attività asincrone sono state eseguite. La durata visualizzata alla riga 6 è la durata totale di esecuzione dello script (codice principale + attività asincrone);

La funzione [setTimeout] ci consentirà di simulare attività asincrone in un ambiente [node.js]. Infatti, la funzione [setTimeout] si comporta come un’attività asincrona:

  • restituisce immediatamente un risultato, in questo caso un numero di timer, tramite il consueto meccanismo delle funzioni (return);
  • può restituire successivamente (cosa che nell’esempio sopra non avviene ancora) altri risultati tramite eventi che vengono poi elaborati dal ciclo di eventi di [node.js];
  • nella maggior parte dei casi che seguiranno, questi eventi saranno due:
    • un evento che potremmo chiamare [success], che verrà emesso dal task asincrono che ha portato a termine con successo ciò che doveva fare. All’evento emesso è associato un dato, ovvero il risultato del task;
    • un evento che potremmo chiamare [failure], che verrà emesso dall’attività asincrona che non è riuscita a portare a termine ciò che doveva fare. All’evento emesso è associato un dato, generalmente un oggetto che descrive l’errore. Esempi di possibili errori in una task Internet asincrona potrebbero essere «rete non disponibile», «server inesistente», «timeout superato», ...
  • il codice principale che ha avviato un’attività asincrona può sottoscriversi agli eventi che tale attività potrebbe emettere. Quando uno di questi viene emesso, il codice principale ne viene avvisato e può attivare l’esecuzione di una funzione specifica destinata a gestire l’evento. Questa funzione riceve come parametro il dato che l’attività asincrona ha associato all’evento emesso;

11.2. script [async-02]

In questo script, la funzione asincrona [setTimeout] emetterà eventi per comunicare dati ai codici che si saranno iscritti a tali eventi.

L’accesso agli eventi di [node.js] richiede librerie aggiuntive. Scegliamo la libreria [events], che installiamo insieme a [npm]:

Image

Lo script [async-02] è il seguente:


'use strict';

// le funzioni asincrone possono restituire un risultato emettendo un evento
// il codice principale può recuperare questi risultati iscrivendosi agli eventi emessi

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

// inizio
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// un generatore di eventi
const eventEmitter = new EventEmitter();

// setTimeout avvia un timer di 1000 ms (2° parametro) e restituisce immediatamente il numero di tale timer
// quando il timer ha esaurito i 1000 ms, emette un evento che viene messo in coda dal runtime
// quando l'evento viene elaborato dal runtime, viene eseguita la funzione (primo parametro)
setTimeout(function () {
  // questo codice verrà eseguito quando il timer avrà raggiunto il valore 0
  console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
  // viene generato un evento per segnalare che è disponibile un risultato
  eventEmitter.emit("timer1Success", { success: 4 });
  // viene emesso un altro evento per segnalare che è disponibile un altro risultato
  eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)

// ci si abbona all'evento [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)));
});

// ci si abbona all'evento [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)));
});

// verrà visualizzato prima dei messaggi degli eventi generati dalla funzione associata a [timer1]
console.log("[fin du code principal du script],", heure(débutScript));

// utilità di visualizzazione dell'ora e della durata
function heure(début) {
  // ora corrente
  const now = moment(Date.now());
  // formattazione dell'ora
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  // è necessario calcolare una durata?
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // formattazione di ora e durata
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // risultato
  return result;
}

Commenti

  • riga 9: si importa la classe [EventEmitter] dalla libreria [events]. Si tratta di una novità: finora avevamo importato solo oggetti letterali e funzioni;
  • riga 15: si crea un generatore di eventi [node.js] istanziando la classe [EventEmitter] con la parola chiave [new];
  • righe 20-27: la funzione asincrona [setTimeout]. Durante la sua esecuzione emetterà due eventi:
    • riga 24, l’evento [timer1Success] con come valore associato l’oggetto {success : 4};
    • riga 26, l’evento [timer1Failure] con come valore associato l’oggetto {failure : 6};
    • una funzione asincrona può generare tutti gli eventi che desidera. Abbiamo detto in precedenza che il più delle volte genera uno dei due eventi [success, failure], non entrambi come in questo caso;
  • riga 20: l’esecuzione di [setTimeout] è istantanea: viene avviato un timer e il suo numero viene restituito al codice chiamante. L’emissione degli eventi avverrà in un secondo momento, in questo caso 1 secondo dopo;
  • l’emissione degli eventi è inutile se non c’è alcun codice in grado di gestirli quando si verificheranno. Ecco perché il codice principale deve iscriversi ai due eventi [timer1Success, timer1Failure] se vuole gestirli, in particolare recuperare i dati associati a tali eventi;
  • righe 30-32: il codice principale si abbona all’evento [timer1Success]. Quando il gestore di eventi di [node.js] elaborerà questo evento, chiamerà la funzione che costituisce il secondo parametro del metodo [eventEmitter.on], passandole il dato (qui denominato [result]) associato all’evento [timer1Success];
  • riga 31: la funzione di gestione dell’evento visualizzerà il jSON del dato associato all’evento e l’ora corrente;
  • righe 35-37: con un codice analogo, il codice principale si abbona all’evento [timer1Failure];
  • l'iscrizione a un evento (1° parametro) non esegue immediatamente il codice della funzione [callback] (2° parametro). Quest'ultima verrà eseguita solo dopo che l'evento avrà avuto luogo;
  • riga 40: il codice principale dello script è terminato, ma non lo script stesso, poiché il codice principale ha avviato un'attività asincrona. Lo script globale terminerà solo al termine di tale attività asincrona;

È quanto dimostrano i risultati ottenuti:


[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

  • riga 3: fine del codice principale 10 ms dopo l’inizio dello script;
  • riga 4: inizio della funzione incapsulata nel timer da 1000 ms, circa 1 secondo dopo l’inizio dello script;
  • riga 5: elaborazione dell’evento [‘timer1Success’], 2 ms dopo;
  • riga 6: elaborazione dell'evento [‘timer1Failure’], 1 ms dopo l'evento [‘timer1Success’];
  • riga 8: fine dello script globale con una durata totale di 1,627 secondi;

11.3. script [async-03]

Lo script seguente mostra un altro aspetto del ciclo di eventi di [node.js]:

  • il ciclo esegue gli eventi uno dopo l’altro, generalmente nell’ordine in cui arrivano. Alcuni OS assegnano delle priorità agli eventi, che vengono quindi elaborati in ordine di priorità e non in ordine di arrivo;
  • il ciclo esegue un solo evento alla volta. Quello successivo viene elaborato solo quando l’elaborazione del precedente è terminata. In un sistema basato sugli eventi, è quindi necessario evitare di scrivere codice che monopolizzi a lungo il processore, poiché in tal caso gli eventi non vengono elaborati nel momento in cui si verificano, ma solo in un secondo momento, quando il ciclo degli eventi arriva a loro. Si ottiene così un’applicazione poco «reattiva»;

Lo script [async-03] mostra un esempio di questo fenomeno:


'use strict';

// le funzioni asincrone possono restituire un risultato emettendo un evento
// il codice principale può recuperare questi risultati iscrivendosi agli eventi emessi

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

// inizio
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// un generatore di eventi
const eventEmitter = new EventEmitter();

// setTimeout avvia un timer di 1000 ms (2° parametro) e restituisce immediatamente il numero di tale timer
// quando il timer ha esaurito i 1000 ms, emette un evento che viene messo in coda dal runtime
// quando l'evento viene elaborato dal runtime, viene eseguita la funzione (primo parametro)
setTimeout(function () {
  // questo codice verrà eseguito quando il timer avrà raggiunto il valore 0
  console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
  // viene generato un evento per segnalare che è disponibile un risultato
  eventEmitter.emit("timer1Success", { success: 4 });
  // viene generato un altro evento per segnalare che è disponibile un altro risultato
  eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)

// ci si abbona all'evento [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)));
});

// ci si abbona all'evento [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)));
});

// un codice sincrono piuttosto intensivo che ha impedito al codice principale di terminare prima della fine di [timer1]
for (let i = 0; i < 1000000; i++) {
  for (let j = 0; j < 10000; j++) {
    i + i ^ 2 + i ^ 3;
  }
}

// verrà visualizzato prima dei messaggi degli eventi emessi dalla funzione associata a [timer1]
console.log("[fin du script],", heure(débutScript));

// utilità di visualizzazione di ora e durata
function heure(début) {
 ...
}

Commenti

  • questo codice è quello dell’esempio precedente [async-02], al quale sono state aggiunte le righe 39-44;
  • righe 20-27: la funzione [setTimeout] è stata programmata per eseguire una funzione asincrona interna dopo un intervallo di un secondo. Trascorso questo secondo, l’esecuzione della funzione asincrona del timer non avviene immediatamente: viene inserito un evento nel ciclo di esecuzione per richiederla. Se il ciclo di esecuzione è occupato nell’elaborazione di un altro evento, l’esecuzione della funzione asincrona del timer dovrà attendere;
  • righe 20-27: non appena la funzione [setTimeout] ha impostato il proprio timer con un intervallo di un secondo, libera il processore e restituisce il controllo al codice chiamante. Quest’ultimo prosegue con le righe 30-37, che sono sottoscrizioni a eventi e hanno un tempo di esecuzione trascurabile;
  • il codice principale prosegue con le righe 40-44, che formano un ciclo di 1010 iterazioni. Questo codice sarà in esecuzione quando il timer emetterà l’evento «fine del tempo di 1 secondo». Tale evento viene quindi inserito nel ciclo degli eventi, ma dovrà attendere la fine dell’esecuzione del codice principale dello script per avere la possibilità di essere elaborato;
  • riga 47: fine del codice principale dello script. È solo dopo questa ultima visualizzazione che l’evento di fine timer potrà essere elaborato e la funzione asincrona interna a [setTimeout] potrà essere eseguita;

Lo script fornisce i seguenti risultati:


[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

Commenti

  • riga 3: si nota che il codice principale dello script ha impiegato 9 secondi per essere eseguito. Gli eventi che potrebbero essersi verificati durante questo periodo sono stati messi in attesa nel ciclo degli eventi;
  • riga 4: si nota che l’evento [fin du timer] è stato elaborato 5 ms dopo la fine del codice principale. È stato emesso circa 1 s dopo l’inizio dello script, ma ha dovuto attendere altri 8 s per essere infine elaborato;

Da questo esempio si evince che, in un sistema basato sugli eventi, un codice non deve mai occupare il processore per troppo tempo. Se si ha un codice sincrono che richiede molto tempo per essere eseguito, è necessario «organizzarsi» per scomporlo in attività asincrone più brevi che segnaleranno il loro completamento tramite un evento.

11.4. script [async-04]

Lo script [async-04] illustra un altro meccanismo, denominato [Promise], ovvero una promessa di risultato. Questo meccanismo evita di gestire esplicitamente gli eventi [node.js]. La gestione avviene implicitamente e lo sviluppatore può quindi ignorare l’esistenza di tali eventi. Comprendere questi meccanismi gli consentirà tuttavia di cogliere meglio il funzionamento dei [Promise], che a prima vista appare complesso.

Il tipo [Promise] è una classe JavaScript. Il suo costruttore accetta come parametro una funzione asincrona alla quale passa due parametri tradizionalmente denominati [resolve] e [reject]. Potrebbero avere un altro nome;

1
2
3
4
5
6
7
const promise=new Promise(function(resolve, reject){
     // viene avviata un'attività asincrona
    
     // in caso di esito positivo: chiamare resolve(result), dove [result] è il risultato dell'attività asincrona;
     // in caso di errore: chiamare reject(error), dove [error] è un oggetto che incapsula l’errore riscontrato;
}
// iscriversi agli eventi emessi dall'attività asincrona di [Promise]
  • il costruttore di [Promise] esegue due operazioni:
    • crea un evento per avviare l’esecuzione della funzione [function(resolve, reject)] che gli è stata passata come parametro, ma non attende il suo risultato e restituisce immediatamente un oggetto [Promise] al codice chiamante. Quest’ultimo può assumere quattro stati:
      • [pending]: l'azione asincrona che ha generato la [Promise] non è ancora terminata;
      • [fulfilled]: l'azione asincrona che ha generato il codice [Promise] si è conclusa con successo;
      • [rejected]: l'azione asincrona che ha generato [Promise] si è conclusa con un errore;
      • [settled]: l'azione asincrona che ha restituito [Promise] è terminata;

Quando il costruttore restituisce il risultato, l’oggetto [Promise] creato si trova nello stato [pending], in attesa dei risultati della funzione asincrona;

  • (continua)
    • l'attività asincrona delle righe 2-5 viene avviata immediatamente. Le attività asincrone sono per lo più attività di I/O asincrone che si articolano come segue:
      1. esecuzione di un codice sincrono per avviare l’operazione di I/O con un altro dispositivo, ad esempio un server remoto;
      2. attesa della risposta da parte di tale componente;
      3. elaborazione di tale risposta;

È la fase 2, quella di attesa del dispositivo esterno al processore, a essere la più dispendiosa. Anziché attendere:

  • la ricezione dei dati richiesti al dispositivo esterno verrà segnalata da un evento;
  • nel codice sincrono che seguirà la fase 1 (riga 7 del codice di esempio), ci si abbonerà a tale evento e poi, a un certo punto, si tornerà nel ciclo degli eventi di [node.js]. Verrà quindi elaborato l’evento successivo nell’elenco degli eventi in attesa;
  • durante la fase 2, vi è parallelismo di esecuzione ma su periferiche diverse:
    • il processore per il ciclo degli eventi;
    • un dispositivo esterno (disco, database, server remoto) per la ricerca dei dati richiesti;
  • al termine della fase 2, quando l’operazione di I/O avrà ottenuto il dato richiesto, verrà emesso un evento per indicare che il risultato dell’I/O è disponibile. Questo evento si aggiungerà quindi agli altri nell’elenco degli eventi in attesa;
  • quando arriverà il suo turno, verrà elaborato. La funzione associata a questo evento (riga 7 del codice di esempio) verrà quindi eseguita;

Questa modalità di funzionamento consente di evitare i tempi morti: ovvero quei momenti in cui il processore attende la risposta da una periferica più lenta di lui;

  • (continua)
    • quando l’attività asincrona delle righe 2 e 5 è stata avviata e ha terminato il proprio lavoro, ha la possibilità di restituire un risultato al codice chiamante, grazie alle due funzioni [resolve, reject] che il costruttore [Promise] le ha passato come parametri. La convenzione è la seguente:
      • il task asincrono segnala il successo tramite [resolve(result)]. Ciò equivale a inserire nel ciclo di eventi di [node.js] un evento che potremmo chiamare [resolved] con [result] come dato associato;
      • l'attività asincrona segnala un errore tramite [reject(error)]. Ciò equivale a inserire nel ciclo di eventi di [node.js] un evento che potremmo chiamare [rejected] con [error] come dato associato, in genere un oggetto che descrive in dettaglio l’errore verificatosi;
      • è quindi necessario che il codice chiamante si abboni a questi due eventi per essere avvisato della disponibilità del risultato della funzione asincrona;

Una volta completata l’esecuzione dell’attività asincrona incapsulata in [Promise], lo stato dell’oggetto [promise] restituito dal costruttore [Promise(…)] cambia:

  • l’evento [resolved] lo fa passare dallo stato [pending] a [resolved];
  • l’evento [rejected] lo fa passare dallo stato [pending] a [rejected];

L'iscrizione agli eventi [resolved] e [rejected] dell'attività asincrona avviene tramite i metodi della classe [Promise] con la seguente sintassi:

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

dove:

  • f1 è una funzione eseguita quando lo stato di [promise] passa da [pending] a [resolved], ovvero quando l'attività asincrona ha completato con successo il proprio lavoro. Riceve come parametro il valore [result], trasmesso dall’istruzione [resolve(result)] dell’attività asincrona;
  • f2 è una funzione eseguita quando lo stato di [promise] passa da [pending] a [rejected], ovvero quando l’attività asincrona non è riuscita a portare a termine il proprio lavoro. Riceve come parametro il valore [error], trasmesso dall’istruzione [reject(errror)] dell’attività asincrona;
  • f3 è una funzione eseguita dopo l’esecuzione dei metodi [then] o [catch], quindi viene sempre eseguita. Non riceve alcun parametro;

Questa sintassi nasconde completamente gli eventi a cui ci si abbona. Si tratta tuttavia di un abbonamento e, come quello dell’esempio precedente, non esegue immediatamente le funzioni [f1, f2, f3]. Queste saranno eseguite o meno quando si verificherà uno degli eventi [resolved, rejected] a cui ci si abbona.

Lo script [async-04] illustra questo meccanismo:


'use strict';

// è possibile ottenere i risultati (successo, errore) di una funzione asincrona
// senza utilizzare esplicitamente gli eventi grazie alla classe [Promise]
// questa classe utilizza implicitamente gli eventi, ma questi non sono visibili nel codice

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

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

// definizione di un'attività asincrona tramite una promessa [Promise]
// l'attività asincrona è il parametro del costruttore [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));
  // codice asincrono
  setTimeout(function () {
    console.log("[fin fonction asynchrone de promise1],", heure(débutPromise1));
    // l'operazione asincrona restituisce un risultato con la funzione [resolve]
    // la promessa viene quindi risolta con successo
    resolve('[réussite]');
  }, 1000)
});

// è possibile conoscere il risultato della promessa [promise1]
// quando questa è stata risolta (resolve) o rifiutata (reject)
// l'istruzione che segue è un abbonamento all'evento [resolved] tramite il metodo [then]
// e all'evento [rejected] tramite il metodo [catch]
// il metodo [finally] viene eseguito sia dopo un then che dopo un catch
promise1.then(result => {
  // in caso di esito positivo della promessa  [evt resolved]
  console.log(sprintf("[promise1.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
  // caso di errore  [evt rejected]
  console.log(sprintf("[promise1.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
  // eseguito in ogni caso
  console.log("[promise1.finally]", heure(débutPromise1));
});

// definizione di un'attività asincrona utilizzando una promessa [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));
  // attività asincrona
  setTimeout(function () {
    console.log("[fin fonction asynchrone de promise2],", heure(débutPromise2));
    // l'attività asincrona restituisce un risultato con la funzione [reject]
    // la promessa viene quindi disattesa
    reject('[échec]');
  }, 2000)
});

// è possibile conoscere il risultato della promessa [promise2]
// quando questa è stata risolta (resolve) o rifiutata (reject)
promise2.then(result => {
  // in caso di esito positivo della promessa [evt resolved]
  console.log(sprintf("[promise2.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
  // in caso di errore [evt rejected]
  console.log(sprintf("[promise2.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
  // eseguito in tutti i casi
  console.log(sprintf("[promise2.finally], %s", heure(débutPromise2)));
});

// verrà visualizzato prima dei messaggi delle funzioni asincrone e di quelli degli eventi associati
console.log("[fin du code principal du script],", heure(débutScript));

// utilità
function heure(début) {
  // ora corrente
  const now = moment(Date.now());
  // formattazione dell'ora
  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);
    // formattazione della durata
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // risultato
  return result;
}

Commenti

  • righe 18-28: creazione di un [Promise promise1]. La sua funzione asincrona restituisce il risultato tramite un evento dopo un secondo. Una volta avviata questa operazione asincrona (attivazione di un timer), non si attende che restituisca il risultato e si passa immediatamente al codice della riga 35;
  • righe 35-44: ci si abbona ai due eventi [resolved, rejected] che la funzione asincrona interna a [promise1] può emettere;
  • righe 46-71: si ripete la stessa sequenza di codice di prima per una seconda promessa [promise2];
  • riga 74: il codice principale dello script è terminato, ma non lo script nel suo complesso, poiché sono state avviate due azioni asincrone. Si ritorna al ciclo degli eventi, dove a un certo punto si verificherà uno degli eventi [resolved, rejected] delle promesse [promise1, promise2]. A quel punto verrà elaborato;
  • poi si tornerà al ciclo degli eventi. A quel punto, il secondo evento [resolved, rejected] delle promesse [promise1, promise2] verrà elaborato quando si verificherà;

Esecuzione


[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

Commenti

  • riga 3: viene avviata la funzione asincrona di [promise1], ma non se ne attende la fine, che sarà segnalata da un evento;
  • riga 4: viene avviata la funzione asincrona di [promise2], ma non se ne attende la conclusione, che sarà segnalata da un evento;
  • riga 5: fine del codice principale e ritorno al ciclo degli eventi;
  • riga 6: elaborazione dell’evento [fin fonction asynchrone de promise1]. Lo stato di [promise1] passerà a [resolved]. Un evento lo segnala;
  • riga 7: poiché [promise2] non ha ancora terminato il proprio lavoro, l’evento [promise1 resolved], appena inserito nel ciclo, verrà elaborato dal metodo [promise1.then] e successivamente dal metodo [promise.finally] (riga 8);
  • righe 9-11: lo stesso meccanismo si verifica quando [promise2] passa dallo stato [pending] a [resolved];

11.5. script [async-05]

Torniamo al codice del costruttore di un oggetto [Promise]:

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

const promise=new Promise(function(resolve, reject){
// viene avviata un'operazione asincrona
// …
// in caso di esito positivo: chiamare resolve(result) dove [result] è il risultato dell'attività asincrona;
// in caso di errore: chiamare reject(error) dove [error] è un oggetto che incapsula l'errore riscontrato;
}
// ci si abbona agli eventi emessi dall'attività asincrona

Alla riga 2 viene avviata l’attività asincrona di [Promise]. Spesso questa attività richiede più parametri rispetto ai soli parametri [resolve, reject] che le vengono passati dalla funzione che la incapsula. In questo caso, si incapsula la creazione di [Promise] in una funzione che le passerà i parametri necessari alla sua funzione asincrona:

'use strict';

// definizione della funzione asincrona
function uneFonctionAsynchrone (p1, p2, , pn){
 return new Promise(function(resolve, reject){
     // viene avviata un'attività asincrona con i parametri (P1, p2, …, pn)
    // 
     // in caso di esito positivo: chiamare resolve(result), dove [result] è il risultato dell'attività asincrona;
     // in caso di errore: chiamare reject(error), dove [error] è un oggetto che incapsula lerrore riscontrato;
}
// ci sisi sottoscrivono gli eventi [resolved, rejected] che verranno emessi dalla funzione asincrona [uneFonctionAsynchrone]

// qualche tempo dopo, viene chiamata la funzione asincrona [uneFonctionAsynchrone]
uneFonctionAsynchrone(e1, e2, , en) ;

Il seguente script:

  • definisce due funzioni asincrone che restituiscono un [Promise];
  • ne avvia l'esecuzione in parallelo e attende che entrambe siano terminate per eseguire una determinata operazione;

'use strict';

// È possibile definire funzioni asincrone che restituiscono un tipo [Promise]
// e possono quindi essere contrassegnate con la parola chiave [async]

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

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

// una funzione asincrona che restituisce una promessa [Promise]
function async01(p1) {
  return new Promise((resolve) => {
    console.log("[début de la tâche asynchrone async01]");
    // l'attività asincrona
    const débutAsync01 = moment(Date.now());
    setTimeout(function () {
      console.log("[fin de la tâche asynchrone async01],", heure(débutAsync01));
      // l'attività asincrona può restituire un risultato complesso
      resolve({
        prop1: [10, 20, 30],
        prop2: "abcd",
        prop3: p1,
      });
    }, 1000)
  });
}

// una funzione asincrona che restituisce una promessa [Promise]
function async02(p1, p2) {
  return new Promise(resolve => {
    console.log("[début de la tâche asynchrone async02]");
    // attività asincrona
    const débutAsync02 = moment(Date.now());
    setTimeout(function () {
      console.log("[fin de la tâche asynchrone async02],", heure(débutAsync02));
      // l'operazione asincrona può restituire un risultato complesso
      resolve({
        prop1: [11, 21, 31],
        prop2: "xyzt",
        prop3: p1 + p2
      });
    }, 2000)
  })
}

// si lanciano le due funzioni asincrone in parallelo
// e si attende che entrambe abbiano terminato
// il blocco "then" viene eseguito solo se entrambe le funzioni hanno generato l'evento [resolved]
// il "catch" viene eseguito non appena una delle due funzioni genera l'evento [rejected]
Promise.all([async01(10), async02(10, 20)])
  //; il risultato è un array [result1, result2] dove [result1] è il risultato generato da un [resolve] di [async01]
  // e [result2] è il risultato emesso da un [resolve] di [async02]
  .then(result => {
    console.log(sprintf("[promise-all success], %s, result=%j", heure(débutScript), result));
  })
  // error è il risultato generato dal primo [reject] di una delle due funzioni asincrone
  .catch(error => {
    console.log(sprintf("[promise-all error], %s, erreur=%j", heure(débutScript), error));
  })
  // finally viene eseguito dopo il then o il catch
  .finally(() => {
    console.log(sprintf("[promise-all finally], %s", heure(débutScript)));
  });

// verrà visualizzato prima dei messaggi delle funzioni asincrone e degli eventi associati
console.log("[fin du code principal du script],", heure(débutScript));

// utilità
function heure(début) {
  // ora corrente
  const now = moment(Date.now());
  // formattazione dell'ora
  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);
    // formattazione della durata
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // risultato
  return result;
}

Commenti

  • righe 15-30: si definisce una funzione [async01] che restituisce il risultato dopo 1 secondo tramite un evento timer. La funzione [async01] viene utilizzata nel risultato alla riga 26;
  • righe 33-47: si procede allo stesso modo con una funzione [async02] che restituisce il risultato dopo 2 secondi tramite un evento timer. La funzione [async02] accetta due parametri che vengono utilizzati nel risultato alla riga 44;
  • quando verranno chiamate, entrambe le funzioni [async01, async02]:
    • verranno avviate;
    • restituiranno al codice chiamante due promesse [promise1, promise2];
    • l’esecuzione tornerà quindi al codice chiamante, che proseguirà il proprio corso;
    • dopo circa 1 secondo, [async01] emetterà un evento per segnalare che ha terminato il proprio lavoro. L’evento in questione verrà messo in attesa nel ciclo di eventi associato al risultato trasmesso da [async01] insieme all’evento;
    • dopo circa 2 secondi, lo stesso processo si ripeterà per [async02];
  • riga 54: solo ora vengono eseguite le funzioni asincrone [async01, async02] (notazioni async01(10) e async02(10,20)). Esse vengono eseguite all’interno di un array passato come parametro al metodo [Promise.all]. È noto che entrambe le funzioni [async01, async02] restituiscono una promessa al codice chiamante. Pertanto, il parametro di [Promise.all] è un array di due promesse;
  • [Promise.all([promise1, promise2, …, promisen]).then(f1).catch(f2).finally(f3)] è una sottoscrizione agli eventi:
    • [Promise.all] è di tipo [Promise];
    • la funzione [f1] del metodo [then] verrà eseguita quando tutte le promesse [promise1, promise2, …, promisen] dell’array dei parametri del metodo [all] saranno passate dallodallo stato [pending] allo stato [resolved]. In altre parole, [f1] verrà eseguita quando tutte le promesse dell’array saranno state completate con successo;
    • la funzione [f2] del metodo [catch] verrà eseguita non appena una delle promesse dell’array passerà dallo stato [pending] allo stato [rejected]. In altre parole, [f2] viene eseguita non appena una delle promesse dell’array fallisce;
    • la funzione [f3] del metodo [finally] verrà eseguita dopo l’esecuzione di uno dei metodi [then, catch], quindi verrà sempre eseguita;

L'esecuzione del codice produce i seguenti risultati:


[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
  • righe 6-7: vengono avviate le due attività asincrone [async01, async02]. Esse funzionano in parallelo. Non è l’esecuzione del loro codice a avvenire in parallelo, ma le rispettive attese dei dati richiesti si svolgono contemporaneamente;
  • riga 5: il codice principale dello script è terminato. Resta da attendere la fine delle due attività asincrone [async01, async02];
  • riga 6: l’attività asincrona [async01] termina circa 1 s dopo il suo avvio. Restituisce un risultato tramite la funzione [resolve], pertanto la sua promessa nella tabella alla riga 56 del codice passa dallo stato [pending] a [resolved]. Ciò non è sufficiente per attivare il metodo [then], righe 59-60 del codice;
  • riga 7: l'attività asincrona [async02] termina circa 2 s dopo il suo avvio. Restituisce un risultato tramite la funzione [resolve], pertanto la sua promessa nella tabella alla riga 56 del codice passa dallo stato [pending] a [resolved]. Il metodo [then] verrà eseguito non appena il ciclo degli eventi lo consentirà;
  • riga 8: viene eseguito il metodo [then] da [Promise.all]. Riceve come parametro un array [result1, result2], dove [result1] è il risultato emesso da [async01], e [result2] quello emesso da [async02];
  • riga 9: viene eseguito il metodo [finally] di [Promise.all];

11.6. script [async-06]

Questo nuovo script mostra come l’uso combinato delle parole chiave [async / await] consenta di ottenere un codice asincrono simile a uno sincrono. La gestione degli eventi è completamente nascosta e la comprensione del codice è facilitata.

Riprendiamo l’esempio precedente apportando le seguenti modifiche:

  • aggiungiamo una terza funzione asincrona [async03] che restituisce il proprio risultato tramite il metodo [Promise.reject], il quale segnala quindi al ciclo di eventi che non è riuscita a portare a termine il proprio compito;
  • si eseguono in sequenza le tre funzioni asincrone [async01, async02, async03]. Nell’esempio precedente, le funzioni asincrone [async01, async02] erano state eseguite in parallelo;
  • prima della comparsa delle parole chiave [async/await], l’esecuzione sequenziale delle azioni asincrone avveniva tramite [Promise] annidate l’una nell’altra. Non appena c’erano più azioni asincrone da eseguire in questo modo, il numero di promesse aumentava di conseguenza e il codice diventava meno leggibile;
  • con le parole chiave [async/await], l’esecuzione sequenziale delle attività asincrone avviene con una sintassi simile a quella dell’esecuzione delle attività sincrone:

// funzione asincrona - utilizzo di async / await
async function main() {
  // esecuzione sequenziale delle attività asincrone
  try {
    // esecuzione in attesa di [async01]
    const result1 = await async01(...);
    console.log("[async01 result]=", result1);
    // esecuzione in attesa di [async02]
    const result2 = await async02(...);
    console.log("[async02 result]=", result2);
    // esecuzione in attesa di [async03]
    const result3 = await async03(...);
    console.log("[async03 result]=", result3);
  } catch (error) {
    // una delle operazioni asincrone non è andata a buon fine
    console.log(sprintf("[sequential error]= %j, %s", error));
  } finally {
    // completato
    console.log("[fin exécution séquentielle des tâches asynchrones],");
  }
  • riga 6: viene avviata la funzione asincrona [async01] (parola chiave await) e si attende che pubblichi il proprio risultato tramite uno dei metodi [Promise.resolve, Promise.reject]. Si tratta quindi di un’operazione bloccante;
  • riga 6: la parola chiave [await] trasforma l’operazione asincrona [async01] in un’operazione bloccante. Sappiamo che l’operazione [async01] restituisce un risultato in due modi:
    • restituisce al codice chiamante, quasi immediatamente, un oggetto [Promise];
    • pubblica successivamente un risultato sul ciclo degli eventi tramite i metodi [Promise.resolve, Promise.reject]. È proprio quest’ultimo risultato che viene recuperato da [result1], riga 6. La gestione degli eventi dell’azione [async01] è diventata invisibile;
    • se il risultato [result] di [async01] viene pubblicato da [Promise.resolve(result)], viene assegnato a [result1] alla riga 6 e l’esecuzione prosegue alla riga 7;
    • Se il risultato di [async01] viene pubblicato da [Promise.reject], ciò provoca un'eccezione e l'esecuzione del codice passa alla riga 14, quella del catch. Il parametro della clausola [catch] è l’oggetto di errore (error) pubblicato da [async01] con un’espressione [Promise.reject(error)]. L’attività asincrona può anche generare l’errore tramite un [throw(error)]. L’oggetto [error] è quello recuperato in [catch(error)];
    • la parola chiave [await] deve obbligatoriamente trovarsi in una funzione preceduta dalla parola chiave [async], riga 2. Questa parola chiave indica che la funzione [main] è una funzione asincrona;
    • nell’espressione [await f(…)], [f] deve essere una funzione asincrona che restituisce un oggetto [Promise] al codice chiamante;
  • si ripete la stessa operazione per l’azione asincrona [async02], riga 9, e [async03], riga 12;

Sempre utilizzando le parole chiave [async / await], è possibile eseguire in parallelo attività asincrone con la seguente sintassi:


try {
    // esecuzione parallela delle attività asincrone
    const result = await Promise.all([async01(...), async02(...), async03(...)]);
    console.log(sprintf("[parallel success], %s, result=%j", heure(débutParallel), result));
  } catch (error) {
    // una delle azioni asincrone non è andata a buon fine
    console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
  } finally {
    // completato
    console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
}
  • riga 3: si ha un'operazione bloccante: si attende che le tre attività asincrone dell'array [async01(..), async02(..), async03(..)] abbiano pubblicato i propri risultati sul ciclo di eventi con uno dei metodi [Promise.resolve, Promise.reject];
  • se le tre attività asincrone pubblicano i propri risultati con [Promise.resolve], la costante [result] è quindi l’array [result1, result2, result3] dove:
    • [result1] è il risultato pubblicato da [async01] con l’espressione [Promise.resolve(result1)];
    • [result2] è il risultato pubblicato da [async02] con l’espressione [Promise.resolve(result2)];
    • [result3] è il risultato pubblicato da [async03] con l'espressione [Promise.resolve(result3)];
  • se una delle tre attività pubblica il proprio risultato con l’espressione [Promise.reject(error)], si verifica un’eccezione;
    • la costante [result] della riga 3 non riceve il proprio valore;
    • l'esecuzione passa direttamente a [catch] della riga 5;
    • il parametro (error) del catch è l’oggetto (error) restituito dall’espressione [Promise.reject(error)];

Combinando queste due sintassi, è possibile eseguire indifferentemente attività asincrone in modo sequenziale o parallelo, il tutto con una sintassi analoga a quella di un codice sincrono. È quindi preferibile utilizzare questa sintassi, molto più leggibile rispetto alle precedenti. Questa sintassi [async / await] è disponibile solo a partire dalla versione 6 di ECMAScript. Esistono ancora molti codici JavaScript che utilizzano le promesse [Promise]. Per questo motivo è importante comprendere anche il loro funzionamento.

Il codice completo dello script [async-06] è il seguente:


'use strict';

// esecuzione parallela o sequenziale di più attività asincrone
// con le parole chiave async / await

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

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

// una funzione asincrona che restituisce un [Promise]
function async01(débutAsync01) {
  return new Promise(function (resolve) {
    console.log("[début fonction asynchrone async01],", heure());
    // funzione asincrona
    setTimeout(function () {
      console.log("[fin fonction asynchrone async01],", heure(débutAsync01));
      // l'azione asincrona può restituire un risultato complesso
      // qui successo
      resolve({
        prop1: [11, 21, 31],
        prop2: "abcd"
      });
    }, 1000)
  });
}

// una funzione asincrona che restituisce un [Promise]
function async02(débutAsync02) {
  console.log("[début fonction asynchrone async02],", heure());
  return new Promise(function (resolve) {
    // funzione asincrona
    setTimeout(function () {
      console.log("[fin fonction asynchrone async02],", heure(débutAsync02));
      // l'azione asincrona può restituire un risultato complesso
      // qui successo
      resolve({
        prop1: [12, 22, 32],
        prop2: "xyzt"
      });
    }, 2000)
  })
}

// una funzione asincrona che restituisce un [Promise]
function async03(débutAsync03) {
  console.log("[début fonction asynchrone async03],", heure());
  return new Promise((resolve, reject) => {
    // funzione asincrona
    setTimeout(function () {
      console.log("[fin fonction asynchrone async03],", heure(débutAsync03));
      // l'azione asincrona può restituire un risultato complesso
      // qui si verifica un errore
      reject({
        prop1: [13, 23, 33],
        prop2: "échec"
      });
    }, 3000)
  })
}

// funzione asincrona - utilizzo di async / await
async function main() {
  const débutSequential = moment(Date.now());
  // esecuzione sequenziale delle attività asincrone
  console.log("------------ exécution séquentielle des tâches asynchrones lancée ------------------------")
  try {
    // esecuzione in attesa di [async01]
    const débutAsync01 = moment(Date.now());
    const result1 = await async01(débutAsync01);
    console.log("[async01 result]=", result1);
    // esecuzione in attesa di [async02]
    const débutAsync02 = moment(Date.now());
    console.log("début async02-------------", heure());
    const result2 = await async02(débutAsync02);
    console.log("[async02 result]=", result2);
    // esecuzione in attesa di [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) {
    // una delle operazioni asincrone non è andata a buon fine
    console.log(sprintf("[sequential error]= %j, %s", error, heure(débutSequential)));
  } finally {
    // completato
    console.log("[fin exécution séquentielle des tâches asynchrones],", heure(débutSequential));
  }

  const débutParallel = moment(Date.now());
  // esecuzione in parallelo delle attività asincrone
  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) {
    // una delle azioni asincrone non è andata a buon fine
    console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
  } finally {
    // completato
    console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
  }

  // completato
  console.log("[fin de la fonction main],", heure(débutSequential));
}
// esecuzione della funzione asincrona main
main();

// verrà visualizzato prima dei vari messaggi delle funzioni asincrone e dei relativi eventi
console.log("[fin du code principal du script],", heure(débutScript));

// utilità
function heure(début) {
  // ora corrente
  const now = moment(Date.now());
  // formattazione dell'ora
  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);
    // formattazione della durata
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // risultato
  return result;
}

I risultati dell’esecuzione sono i seguenti:


[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