11. Programmazione event-driven e funzioni asincrone

Una funzione asincrona è una funzione la cui esecuzione viene avviata ma il cui risultato non viene atteso. Al termine dell'esecuzione, la funzione asincrona emette un evento e trasmette il proprio risultato tramite tale evento.
Questa modalità di funzionamento è particolarmente adatta all'esecuzione all'interno di un browser web. Infatti, un'applicazione in esecuzione all'interno di un browser è un'applicazione event-driven: l'applicazione reagisce agli eventi, innescati principalmente dall'utente (clic, movimenti del mouse, immissione di testo, ecc.). Le applicazioni JavaScript in esecuzione all'interno di un browser interagiscono con servizi esterni tramite il protocollo HTTP. Le funzioni HTTP native di JavaScript sono asincrone: vengono avviate e la ricezione di una risposta dal servizio esterno richiesto viene segnalata da un evento che viene aggiunto all'insieme di eventi gestiti dall'applicazione.
I seguenti script saranno eseguiti da [node.js] e non da un browser. Anche [node.js] ha un modello di esecuzione basato 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 continua fino al completamento di tali attività. Queste attività emettono un evento al termine. Tali eventi vengono messi in coda nel ciclo di eventi;
- lo script principale deve sottoscrivere questi eventi se vuole recuperare i risultati delle azioni asincrone;
- lo script non è terminato finché tutti gli eventi che ha emesso non sono stati elaborati;
11.1. script [async-01]
Lo script seguente illustra il comportamento di uno script contenente un'azione asincrona.
'use strict';
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// setTimeout arms a 1000 ms timer (2nd parameter) and immediately returns the timer number
// when the timer has run out of 1000 ms, it emits an event which is queued by the runtime
// when the event is processed by the runtime, the function (1st parameter) is executed
setTimeout(function () {
// this code will be executed when the timer reaches value 0
console.log("[fin de l'action asynchrone setTimeout],", heure(débutScript));
}, 1000)
// will be displayed before the internal timer function msg
console.log("[fin du code principal du script],", heure(débutScript));
// time and duration display utility
function heure(début) {
// current time
const now = moment(Date.now());
// time formatting
let result = "heure=" + now.format("HH:mm:ss:SSS");
// is it necessary to calculate a duration?
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// format time + duration
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// result
return result;
}
- riga 4: importiamo la libreria [moment] per formattare le date (riga 27);
- riga 5: importiamo la libreria [sprintf-js] per formattare le durate (riga 34);
- riga 8: registriamo l'ora di inizio dello script;
- riga 9: la visualizziamo utilizzando il metodo [time] nelle righe 20–34;
- righe 14–17: la funzione [setTimeout] ha due parametri (f, duration): f è una funzione che viene eseguita quando sono trascorsi [duration] millisecondi;
- riga 14: quando lo script viene eseguito, viene eseguita la funzione [setTimeout]:
- riga 17: viene avviato un timer da 1000 ms e inizia il conto alla rovescia fino a quando non raggiunge lo 0. La funzione [setTimeout] termina non appena il timer viene inizializzato e inizia il conto alla rovescia. Non attende la fine del conto alla rovescia. Restituisce un numero ID per il timer utilizzato e l'esecuzione passa all'istruzione successiva, la riga 20. Qui, il risultato di [setTimeout] non viene utilizzato;
- riga 16: questo messaggio verrà visualizzato al termine del ritardo di 1000 ms della funzione [setTimeout];
- righe 15–16: la funzione f, primo parametro della funzione [setTimeout], verrà eseguita al termine del ritardo di 1000 ms. Verrà quindi visualizzato il messaggio alla riga 16;
- riga 20: questo messaggio verrà visualizzato prima di quello alla riga 16;
Funzione time:
- riga 23: la funzione accetta un parametro opzionale [time], che rappresenta l'ora di inizio di un'operazione di cui deve visualizzare la durata;
- righe 25–27: l'ora corrente viene calcolata e formattata;
- riga 29: se il parametro [start] è presente, allora deve essere calcolata una durata;
- riga 30: la durata dell'operazione. Il risultato è un numero di millisecondi;
- righe 31–32: questo numero di millisecondi viene suddiviso 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
- riga 4, possiamo vedere che l'azione asincrona [setTimeout] è terminata circa 1 secondo dopo la fine del codice principale dello script;
- Riga 6: il tempo visualizzato alla riga 3 è il momento in cui il codice principale termina. Se il codice principale ha avviato attività asincrone, lo script non è completo finché tutte le attività asincrone non sono state eseguite. La durata visualizzata alla riga 6 è il tempo di esecuzione totale dello script (codice principale + attività asincrone);
La funzione [setTimeout] ci consentirà di simulare operazioni asincrone in un ambiente [node.js]. Infatti, la funzione [setTimeout] si comporta come un'operazione asincrona:
- restituisce immediatamente un risultato — in questo caso, un ID del timer — utilizzando il meccanismo standard della funzione (return);
- potrebbe successivamente (cosa che non è ancora avvenuta nel caso sopra) restituire altri risultati tramite eventi che vengono poi elaborati dal ciclo di eventi [node.js];
- nella maggior parte dei casi che seguono, ci saranno due eventi di questo tipo:
- un evento che potrebbe essere chiamato [success], che verrà emesso dall'attività asincrona che ha completato con successo il proprio compito. Un dato — il risultato dell'attività — è associato all'evento emesso;
- un evento che potrebbe essere chiamato [failure], emesso dall'attività asincrona che non è riuscita a completare il proprio compito. All'evento emesso è associato un dato — tipicamente un oggetto che descrive l'errore. Possibili errori con un'attività Internet asincrona, ad esempio, potrebbero essere «rete non disponibile», «macchina server inesistente», «timeout superato», ...
- il codice principale che ha avviato un'attività asincrona può sottoscrivere gli eventi che questa attività è suscettibile di emettere. Quando uno di questi viene emesso, il codice principale viene notificato e può innescare l'esecuzione di una funzione specifica progettata per gestire l'evento. Questa funzione riceve come parametro i dati 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 i dati al codice che si è iscritto a essi.
L'accesso agli eventi [node.js] richiede librerie aggiuntive. Scegliamo la libreria [events], che installiamo utilizzando [npm]:

Lo script [async-02] è il seguente:
'use strict';
// asynchronous functions can return a result by emitting an event
// the main code can retrieve these results by subscribing to the events issued
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// an event transmitter
const eventEmitter = new EventEmitter();
// setTimeout sets a 1000 ms timer (2nd parameter) and immediately returns the timer number
// when the timer has run out of 1000 ms, it emits an event which is queued by the runtime
// when the event is processed by the runtime, the function (1st parameter) is executed
setTimeout(function () {
// this code will be executed when the timer reaches value 0
console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
// an event is issued to indicate that a result is available
eventEmitter.emit("timer1Success", { success: 4 });
// another event is issued to indicate that another result is available
eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)
// subscribe to evt [timer1Success]
eventEmitter.on('timer1Success', (result) => {
console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Success]", result, heure(débutScript)));
});
// subscribe to evt [timer1Failure]
eventEmitter.on('timer1Failure', (result) => {
console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Failure]", result, heure(débutScript)));
});
// will be displayed before the msg of evts issued by the function associated with [timer1]
console.log("[fin du code principal du script],", heure(débutScript));
// time and duration display utility
function heure(début) {
// current time
const now = moment(Date.now());
// time formatting
let result = "heure=" + now.format("HH:mm:ss:SSS");
// is it necessary to calculate a duration?
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// format time + duration
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// result
return result;
}
Commenti
- riga 9: importiamo la classe [EventEmitter] dalla libreria [events]. Questa è una novità: fino ad ora avevamo importato solo oggetti e funzioni letterali;
- riga 15: creiamo un emettitore di eventi [node.js] istanziando la classe [EventEmitter] con la parola chiave [new];
- Righe 20–27: la funzione asincrona [setTimeout]. Quando viene eseguita, emetterà due eventi:
- riga 24, l'evento [timer1Success] con l'oggetto {success: 4} come valore associato;
- riga 26, l'evento [timer1Failure] con l'oggetto {failure: 6} come valore associato;
- una funzione asincrona può emettere tutti gli eventi che vuole. Abbiamo detto prima che il più delle volte emette uno dei due eventi [success, failure], non entrambi come stiamo facendo qui;
- riga 20: l'esecuzione di [setTimeout] è istantanea: viene impostato un timer e il suo ID viene restituito al codice chiamante. Gli eventi verranno emessi in seguito, in questo caso 1 secondo dopo;
- innescare eventi è inutile se non c'è codice per gestirli quando si verificano. Questo è il motivo per cui il codice principale deve sottoscrivere entrambi gli eventi [timer1Success, timer1Failure] se vuole gestirli, in particolare per recuperare i dati associati a questi eventi;
- Righe 30–32: il codice principale si abbona all'evento [timer1Success]. Quando il listener di eventi [node.js] elabora questo evento, chiamerà la funzione che costituisce il secondo parametro del metodo [eventEmitter.on], passandole i dati (qui denominati [result]) associati all'evento [timer1Success];
- riga 31: la funzione di gestione dell'evento visualizzerà il JSON dei dati associati all'evento e l'ora corrente;
- righe 35–37: utilizzando un codice simile, il codice principale si abbona all'evento [timer1Failure];
- L'iscrizione a un evento (primo parametro) non esegue immediatamente il codice della funzione [callback] (secondo parametro). Questa funzione verrà eseguita solo dopo che l'evento si sarà verificato;
- riga 40: il codice dello script principale è terminato, ma lo script stesso no, poiché il codice principale ha avviato un'attività asincrona. Lo script complessivo non terminerà finché questa attività asincrona non sarà completata;
Ecco cosa mostrano i risultati:
[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'avvio dello script;
- riga 4: inizio della funzione incapsulata nel timer da 1000 ms, circa 1 secondo dopo l'avvio 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]
Il seguente script illustra un altro aspetto del ciclo di eventi [node.js]:
- il ciclo esegue gli eventi uno dopo l'altro, generalmente nell'ordine in cui arrivano. Alcuni sistemi operativi assegnano delle priorità agli eventi, che vengono quindi elaborati in ordine di priorità piuttosto che in ordine di arrivo;
- il ciclo esegue un solo evento alla volta. L'evento successivo viene elaborato solo dopo che quello precedente è terminato. In un sistema event-driven, si dovrebbe quindi evitare di scrivere codice che monopolizzi il processore per lunghi periodi, poiché gli eventi non verranno elaborati quando si verificano, ma più tardi, quando il ciclo di eventi li raggiunge. Ciò si traduce in un'applicazione che non è molto "reattiva";
Lo script [async-03] mostra un esempio di questo fenomeno:
'use strict';
// asynchronous functions can return a result by emitting an event
// the main code can retrieve these results by subscribing to the events issued
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// an event transmitter
const eventEmitter = new EventEmitter();
// setTimeout sets a 1000 ms timer (2nd parameter) and immediately returns the timer number
// when the timer has run out of 1000 ms, it emits an event which is queued by the runtime
// when the event is processed by the runtime, the function (1st parameter) is executed
setTimeout(function () {
// this code will be executed when the timer reaches value 0
console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
// an event is issued to indicate that a result is available
eventEmitter.emit("timer1Success", { success: 4 });
// another event is issued to indicate that another result is available
eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)
// subscribe to evt [timer1Success]
eventEmitter.on('timer1Success', (result) => {
console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Success]", result, heure(débutScript)));
});
// subscribe to evt [timer1Failure]
eventEmitter.on('timer1Failure', (result) => {
console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Failure]", result, heure(débutScript)));
});
// slightly intensive synchronous code that prevented the main code from completing before the end of [timer1]
for (let i = 0; i < 1000000; i++) {
for (let j = 0; j < 10000; j++) {
i + i ^ 2 + i ^ 3;
}
}
// will be displayed before the msg of evts issued by the function associated with [timer1]
console.log("[fin du script],", heure(débutScript));
// time and duration display utility
function heure(début) {
...
}
Commenti
- Questo codice proviene dall'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 ritardo 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 per un ritardo di un secondo, libera il processore e restituisce il controllo al codice chiamante. Il codice chiamante prosegue con le righe 30–37, che sono sottoscrizioni di eventi e hanno un tempo di esecuzione trascurabile;
- il codice principale prosegue con le righe 40–44, che formano un ciclo di 1.010 iterazioni. Questo codice sarà in esecuzione quando il timer attiverà il suo evento “fine del ritardo di 1 secondo”. Questo evento viene quindi inserito nel ciclo degli eventi, ma deve attendere che il codice principale dello script abbia terminato l’esecuzione per avere la possibilità di essere elaborato;
- Riga 47: fine del codice principale dello script. È solo dopo questa visualizzazione finale che è possibile elaborare l'evento di fine timer ed eseguire la funzione asincrona interna di [setTimeout];
Lo script 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-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: vediamo che il codice principale dello script ha impiegato 9 secondi per essere eseguito. Qualsiasi evento verificatosi durante questo periodo è stato messo in coda nel ciclo degli eventi;
- riga 4: vediamo che l'evento [timer end] è stato elaborato 5 ms dopo il completamento del codice principale. È stato emesso circa 1 s dopo l'avvio dello script, ma ha dovuto attendere altri 8 s per essere finalmente elaborato;
Il punto chiave di questo esempio è che in un sistema event-driven, il codice non dovrebbe mai occupare il processore per molto tempo. Se si dispone di codice sincrono che richiede molto tempo per essere eseguito, è necessario trovare un modo per suddividerlo in attività asincrone più brevi che segnalino il loro completamento con un evento.
11.4. script [async-04]
Lo script [async-04] illustra un altro meccanismo, chiamato [Promise], una promessa di un risultato. Questo meccanismo evita la necessità di gestire esplicitamente gli eventi [node.js]. Viene gestito implicitamente e lo sviluppatore può quindi ignorare l'esistenza di questi eventi. Comprendere questi meccanismi, tuttavia, aiuterà lo sviluppatore a cogliere meglio il funzionamento di [Promise], che a prima vista può sembrare complesso.
Il tipo [Promise] è una classe JavaScript. Il suo costruttore accetta come parametro una funzione asincrona, alla quale passa due parametri tradizionalmente chiamati [resolve] e [reject]. Potrebbero avere nomi diversi;
- il costruttore [Promise] fa due cose:
- crea un evento per attivare l'esecuzione della funzione [function(resolve, reject)] che gli viene passata come parametro, ma non aspetta il risultato e restituisce immediatamente un oggetto [Promise] al codice chiamante. Questo oggetto può avere quattro stati:
- [pending]: l'azione asincrona che ha restituito il [Promise] non è ancora completata;
- [fulfilled]: l'azione asincrona che ha restituito il [Promise] si è completata con successo;
- [rejected]: l'azione asincrona che ha restituito il [Promise] ha fallito;
- [settled]: l'azione asincrona che ha restituito la [Promise] è stata completata;
- crea un evento per attivare l'esecuzione della funzione [function(resolve, reject)] che gli viene passata come parametro, ma non aspetta il risultato e restituisce immediatamente un oggetto [Promise] al codice chiamante. Questo oggetto può avere quattro stati:
Quando il costruttore restituisce il suo risultato, l'oggetto [Promise] creato si trova nello stato [pending], in attesa dei risultati della funzione asincrona;
- (continua)
- L'operazione asincrona nelle righe 2-5 viene avviata immediatamente. Le operazioni asincrone sono per lo più operazioni di input/output che si articolano come segue:
- esecuzione di codice sincrono per avviare l'operazione di I/O con un altro componente, come un server remoto;
- attesa di una risposta da quel componente;
- elaborazione di tale risposta;
- L'operazione asincrona nelle righe 2-5 viene avviata immediatamente. Le operazioni asincrone sono per lo più operazioni di input/output che si articolano come segue:
La fase 2 — l'attesa del componente esterno — è quella che richiede più risorse. Anziché attendere:
- la ricezione dei dati richiesti al componente esterno sarà segnalata da un evento;
- nel codice sincrono che segue la fase 1 (riga 7 del codice di esempio), ci abboneremo a questo evento e poi, a un certo punto, torneremo al ciclo di eventi [node.js]. Verrà quindi elaborato l'evento successivo nell'elenco degli eventi in sospeso;
- durante la fase 2, c'è un'esecuzione parallela ma su dispositivi diversi:
- il processore per il ciclo di eventi;
- un componente esterno (disco, database, server remoto) per il recupero dei dati richiesti;
- Al termine della fase 2, una volta che l'operazione di I/O avrà ottenuto i dati richiesti, verrà attivato un evento per indicare che il risultato dell'I/O è disponibile. Questo evento si unirà quindi agli altri nella coda degli eventi;
- quando arriverà il suo turno, verrà elaborato. Verrà quindi eseguita la funzione associata a questo evento (riga 7 del codice di esempio);
Questa modalità di funzionamento aiuta a evitare i tempi di inattività: la situazione in cui il processore attende una risposta da un dispositivo più lento di lui;
- (continua)
- una volta che l'attività asincrona nelle righe 2 e 5 è stata avviata e ha completato il suo lavoro, può restituire un risultato al codice chiamante utilizzando le 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 ad aggiungere un evento al ciclo di eventi [node.js] che potrebbe essere chiamato [resolved], con [result] come dati associati;
- il task asincrono segnala un fallimento tramite [reject(error)]. Ciò equivale ad aggiungere un evento al ciclo di eventi [node.js] che potrebbe essere chiamato [rejected], con [error] come dati associati — tipicamente un oggetto che descrive in dettaglio l'errore verificatosi;
- il codice chiamante deve quindi sottoscrivere questi due eventi per essere avvisato quando il risultato della funzione asincrona è disponibile;
- una volta che l'attività asincrona nelle righe 2 e 5 è stata avviata e ha completato il suo lavoro, può restituire un risultato al codice chiamante utilizzando le due funzioni [resolve, reject] che il costruttore [Promise] le ha passato come parametri. La convenzione è la seguente:
Dopo che l'attività asincrona incapsulata nella [Promise] ha terminato l'esecuzione, lo stato dell'oggetto [promise] restituito dal costruttore [Promise(…)] cambia:
- l'evento [resolved] cambia il suo stato da [pending] a [resolved];
- l'evento [rejected] passa dallo stato [pending] a [rejected];
L'iscrizione agli eventi [resolved] e [rejected] dell'attività asincrona avviene utilizzando 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 il valore [result] come parametro, passato 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 completare il proprio lavoro. Riceve il valore [error] come parametro, passato dall'istruzione [reject(error)] dell'attività asincrona;
- f3 è una funzione che viene 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 iscriviamo. Tuttavia, si tratta di un'iscrizione e, come nell'esempio precedente, non esegue immediatamente le funzioni [f1, f2, f3]. Queste verranno eseguite — o meno — quando si verifica uno degli eventi [resolved, rejected] a cui ci siamo iscritti.
Lo script [async-04] illustra questo meccanismo:
'use strict';
// it is possible to obtain the results (success, failure) of an asynchronous function
// without explicitly using events thanks to the [Promise] class
// this class implicitly uses events, but these are not visible in the code
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure(débutScript));
// definition of an asynchronous task using a promise [Promise]
// the asynchronous task is the [Promise] constructor parameter
const débutPromise1 = moment(Date.now());
const promise1 = new Promise(function (resolve) {
// log
console.log("[début fonction asynchrone de promise1],", heure(débutPromise1));
// asynchronous code
setTimeout(function () {
console.log("[fin fonction asynchrone de promise1],", heure(débutPromise1));
// the asynchronous task returns a result with the [resolve] function
// the promise is fulfilled
resolve('[réussite]');
}, 1000)
});
// we can know the result of the promise [promise1]
// when it has been resolved or rejected
// the following instruction is a subscription to the [resolved] event via the [then] method
// and to the [rejected] event via the [catch] method
// the [finally] method is executed whether after a then or a catch
promise1.then(result => {
// promise success stories [evt resolved]
console.log(sprintf("[promise1.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
// error case [evt rejected]
console.log(sprintf("[promise1.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
// executed in all cases
console.log("[promise1.finally]", heure(débutPromise1));
});
// definition of an asynchronous task using a promise [Promise]
const débutPromise2 = moment(Date.now());
const promise2 = new Promise(function (resolve, reject) {
// log
console.log("[début fonction asynchrone de promise2],", heure(débutPromise1));
// asynchronous task
setTimeout(function () {
console.log("[fin fonction asynchrone de promise2],", heure(débutPromise2));
// the asynchronous task returns a result with the [reject] function
// the promise is lost
reject('[échec]');
}, 2000)
});
// we can know the result of the promise [promise2]
// when it has been resolved or rejected
promise2.then(result => {
// promise success stories [evt resolved]
console.log(sprintf("[promise2.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
// error case [evt rejected]
console.log(sprintf("[promise2.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
// executed in all cases
console.log(sprintf("[promise2.finally], %s", heure(débutPromise2)));
});
// will be displayed before the msg of asynchronous functions and those of associated events
console.log("[fin du code principal du script],", heure(débutScript));
// utility
function heure(début) {
// current time
const now = moment(Date.now());
// time formatting
let result = "heure=" + now.format("HH:mm:ss:SSS");
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// formatting duration
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// result
return result;
}
Commenti
- righe 18–28: creazione di una [Promise promise1]. La sua funzione asincrona restituisce il risultato tramite un evento dopo un secondo. Una volta avviata questa operazione asincrona (il timer è avviato), non attendiamo che restituisca il risultato e procediamo immediatamente al codice alla riga 35;
- righe 35–44: ci iscriviamo ai due eventi [resolved, rejected] che la funzione asincrona interna di [promise1] può emettere;
- righe 46–71: ripetiamo la stessa sequenza di codice di prima per una seconda promessa [promise2];
- Riga 74: il corpo principale dello script ha terminato l'esecuzione, ma lo script nel suo complesso no, poiché sono state avviate due azioni asincrone. Torniamo al ciclo degli eventi, dove a un certo punto si verificherà uno degli eventi [resolved, rejected] per le promesse [promise1, promise2]. Verrà quindi gestito;
- poi torniamo al ciclo degli eventi. E lì, il secondo evento [resolved, rejected] delle promesse [promise1, promise2] verrà gestito 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 si attende il suo completamento, che sarà segnalato da un evento;
- riga 4: viene avviata la funzione asincrona di [promise2], ma non si attende il suo completamento, che sarà segnalato da un evento;
- riga 5: fine del codice principale e ritorno al ciclo degli eventi;
- riga 6: elaborazione dell'evento [fine della funzione asincrona di promise1]. Lo stato di [promise1] cambierà in [resolved]. Un evento lo segnala;
- riga 7: poiché [promise2] non ha ancora terminato il suo lavoro, l'evento [promise1 resolved] appena aggiunto al ciclo verrà gestito dal metodo [promise1.then] e poi 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 dell'oggetto [Promise]:
Riga 2: viene avviata l'attività asincrona della [Promise]. Spesso richiede più parametri oltre ai soli [resolve, reject] passati dalla funzione che la incapsula. In questo caso, incapsuliamo la creazione della [Promise] in una funzione che le passerà i parametri necessari alla sua funzione asincrona:
Il seguente script:
- definisce due funzioni asincrone che restituiscono una [Promise];
- avvia la loro esecuzione in parallelo e attende che entrambe siano terminate prima di eseguire una determinata operazione;
'use strict';
// we can define asynchronous functions that render a [Promise] type
// they can then be tagged with the keyword [async]
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// an asynchronous function that renders a promise [Promise]
function async01(p1) {
return new Promise((resolve) => {
console.log("[début de la tâche asynchrone async01]");
// the asynchronous task
const débutAsync01 = moment(Date.now());
setTimeout(function () {
console.log("[fin de la tâche asynchrone async01],", heure(débutAsync01));
// the asynchronous task can render a complex result
resolve({
prop1: [10, 20, 30],
prop2: "abcd",
prop3: p1,
});
}, 1000)
});
}
// an asynchronous function that renders a promise [Promise]
function async02(p1, p2) {
return new Promise(resolve => {
console.log("[début de la tâche asynchrone async02]");
// asynchronous task
const débutAsync02 = moment(Date.now());
setTimeout(function () {
console.log("[fin de la tâche asynchrone async02],", heure(débutAsync02));
// the asynchronous task can render a complex result
resolve({
prop1: [11, 21, 31],
prop2: "xyzt",
prop3: p1 + p2
});
}, 2000)
})
}
// run the two asynchronous functions in parallel
// and wait for them both to finish
// the then executes only if both functions have issued the [resolved] event
// the catch is executed as soon as one of the two functions issues the [rejected] event
Promise.all([async01(10), async02(10, 20)])
// the result is an array [result1, result2] where [result1] is the result emitted by a [resolve] of [async01]
// and [result2] the result emitted by a [resolve] of [async02]
.then(result => {
console.log(sprintf("[promise-all success], %s, result=%j", heure(débutScript), result));
})
// error is the result emitted by the first [reject] of one of the two asynchronous functions
.catch(error => {
console.log(sprintf("[promise-all error], %s, erreur=%j", heure(débutScript), error));
})
// finally is executed after then or catch
.finally(() => {
console.log(sprintf("[promise-all finally], %s", heure(débutScript)));
});
// will be displayed before msgs for asynchronous functions and associated events
console.log("[fin du code principal du script],", heure(débutScript));
// utility
function heure(début) {
// current time
const now = moment(Date.now());
// time formatting
let result = "heure=" + now.format("HH:mm:ss:SSS");
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// formatting duration
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// result
return result;
}
Commenti
- Righe 15–30: Definiamo 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: Facciamo lo stesso 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 suo risultato alla riga 44;
- Quando vengono chiamate le due funzioni [async01] e [async02]:
- verrà avviato;
- verranno restituite due promesse [promise1, promise2] al codice chiamante;
- l'esecuzione tornerà quindi al codice chiamante, che continuerà a funzionare;
- dopo circa 1 secondo, [async01] emetterà un evento per indicare che ha completato il suo lavoro. L'evento in questione verrà messo in coda nel ciclo di eventi associato al risultato passato da [async01] insieme all'evento;
- dopo circa 2 secondi, lo stesso processo si verificherà per [async02];
- Riga 54: Solo ora vengono eseguite le funzioni asincrone [async01, async02] (notazioni async01(10) e async02(10,20)). Vengono eseguite all'interno di un array passato come parametro al metodo [Promise.all]. Sappiamo che [async01, async02] restituiscono entrambe 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 a un evento:
- [Promise.all] è di tipo [Promise];
- la funzione [f1] del metodo [then] verrà eseguita quando tutte le promesse [promise1, promise2, …, promisen] nell'array di parametri del metodo [all] saranno passate dallo stato [pending] allo stato [resolved]. In altre parole, [f1] verrà eseguita quando tutte le promesse nell'array saranno state completate con successo;
- la funzione [f2] del metodo [catch] verrà eseguita non appena una qualsiasi delle promesse nell'array passerà dallo stato [pending] allo stato [rejected]. In altre parole, [f2] viene eseguita non appena una qualsiasi delle promesse nell'array fallisce;
- la funzione [f3] del metodo [finally] verrà eseguita dopo l'esecuzione di uno dei metodi [then, catch], quindi viene 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 vengono eseguite in parallelo. Non è l'esecuzione del loro codice a avvenire in parallelo, ma le rispettive attese per i dati richiesti avvengono contemporaneamente;
- Riga 5: il corpo principale dello script è terminato. Ora dobbiamo solo attendere il completamento delle due attività asincrone [async01, async02];
- riga 6: l'attività asincrona [async01] si completa circa 1 secondo dopo il suo avvio. Restituisce un risultato utilizzando la funzione [resolve], quindi la sua promessa nell'array alla riga 56 del codice passa dallo stato [pending] a [resolved]. Questo non è sufficiente per attivare il metodo [then], righe 59–60 del codice;
- riga 7: l'attività asincrona [async02] si completa circa 2 secondi dopo il suo avvio. Restituisce un risultato utilizzando la funzione [resolve], quindi la sua promessa nell'array alla riga 56 del codice passa dallo stato [pending] a [resolved]. Il metodo [then] verrà eseguito non appena il ciclo di eventi lo consentirà;
- riga 8: viene eseguito il metodo [then] di [Promise.all]. Esso riceve come parametro un array [result1, result2] dove [result1] è il risultato restituito da [async01], e [result2] è il risultato restituito 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 scrivere codice asincrono che assomiglia al codice sincrono. La gestione degli eventi è completamente nascosta, rendendo il codice più facile da comprendere.
Riprendiamo l'esempio precedente con le seguenti modifiche:
- aggiungiamo una terza funzione asincrona [async03], che restituisce il proprio risultato utilizzando il metodo [Promise.reject], segnalando così al ciclo di eventi che non è riuscita a completare il proprio compito;
- eseguiamo le tre funzioni asincrone [async01, async02, async03] in sequenza. Nell'esempio precedente, avevamo eseguito le funzioni asincrone [async01, async02] in parallelo;
- Prima dell'introduzione delle parole chiave [async/await], l'esecuzione sequenziale delle azioni asincrone veniva ottenuta utilizzando oggetti [Promise] annidati. Ogni volta che 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 utilizza una sintassi simile a quella dell'esecuzione delle attività sincrone:
// asynchronous function - using async / await
async function main() {
// sequential execution of asynchronous tasks
try {
// execution while waiting for [async01]
const result1 = await async01(...);
console.log("[async01 result]=", result1);
// execution while waiting for [async02]
const result2 = await async02(...);
console.log("[async02 result]=", result2);
// execution while waiting for [async03]
const result3 = await async03(...);
console.log("[async03 result]=", result3);
} catch (error) {
// one of the asynchronous actions has failed
console.log(sprintf("[sequential error]= %j, %s", error));
} finally {
// completed
console.log("[fin exécution séquentielle des tâches asynchrones],");
}
- riga 6: viene avviata la funzione asincrona [async01] (utilizzando la parola chiave await) e si attende che restituisca il risultato tramite uno dei metodi [Promise.resolve, Promise.reject]. Si tratta quindi di un'operazione di blocco;
- riga 6: la parola chiave [await] trasforma l'operazione asincrona [async01] in un'operazione di blocco. Sappiamo che l'operazione [async01] restituisce un risultato in due modi:
- restituisce un oggetto [Promise] al codice chiamante quasi immediatamente;
- pubblica successivamente un risultato al ciclo di eventi tramite i metodi [Promise.resolve, Promise.reject]. È proprio quest'ultimo risultato che [result1] recupera alla riga 6. La gestione event-driven dell'azione [async01] è diventata invisibile;
- se il risultato [result] di [async01] viene pubblicato tramite [Promise.resolve(result)], viene assegnato a [result1] alla riga 6 e l'esecuzione prosegue alla riga 7;
- Se il risultato di [async01] viene risolto tramite [Promise.reject], ciò genera un'eccezione e l'esecuzione del codice prosegue alla riga 14, il blocco catch. Il parametro della clausola [catch] è l'oggetto di errore (error) risolto da [async01] utilizzando l'espressione [Promise.reject(error)]. L'attività asincrona può anche generare l'errore tramite un [throw(error)]. L'oggetto [error] è quello intercettato in [catch(error)];
- la parola chiave [await] deve trovarsi all'interno di 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;
- Facciamo lo stesso per le azioni asincrone [async02] alla riga 9 e [async03] alla riga 12;
Sempre utilizzando le parole chiave [async / await], è possibile eseguire attività asincrone in parallelo utilizzando la seguente sintassi:
try {
// parallel execution of asynchronous tasks
const result = await Promise.all([async01(...), async02(...), async03(...)]);
console.log(sprintf("[parallel success], %s, result=%j", heure(débutParallel), result));
} catch (error) {
// one of the asynchronous actions has failed
console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
} finally {
// completed
console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
}
- riga 3: abbiamo un'operazione di blocco: attendiamo che le tre attività asincrone nell'array [async01(..), async02(..), async03(..)] pubblichino i loro risultati sul ciclo di eventi utilizzando uno dei metodi [Promise.resolve, Promise.reject];
- se le tre attività asincrone pubblicano i loro risultati utilizzando [Promise.resolve], la costante [result] è quindi l'array [result1, result2, result3] dove:
- [result1] è il risultato pubblicato da [async01] utilizzando l'espressione [Promise.resolve(result1)];
- [result2] è il risultato pubblicato da [async02] utilizzando l'espressione [Promise.resolve(result2)];
- [result3] è il risultato pubblicato da [async03] utilizzando l'espressione [Promise.resolve(result3)];
- se una qualsiasi delle tre attività pubblica il proprio risultato utilizzando l'espressione [Promise.reject(error)], si verifica un'eccezione;
- la costante [result] alla riga 3 non riceve alcun valore;
- l'esecuzione procede direttamente al blocco [catch] alla riga 5;
- il parametro (error) del catch è l'oggetto (error) pubblicato dall'espressione [Promise.reject(error)];
Combinando queste due sintassi, possiamo eseguire attività asincrone in modo sequenziale o in parallelo, il tutto utilizzando una sintassi simile a quella del codice sincrono. Dovremmo quindi preferire questa sintassi, che è molto più leggibile rispetto alle precedenti. Questa sintassi [async/await] è disponibile solo a partire dalla versione 6 di ECMAScript. Esiste ancora molto codice JavaScript che utilizza le promesse [Promise]. Ecco perché è importante anche capire come funzionano.
Il codice completo per lo script [async-06] è il seguente:
'use strict';
// parallel or sequential execution of several asynchronous tasks
// with the keywords async / await
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// start
const débutScript = moment(Date.now());
console.log("[début du code principal du script],", heure());
// an asynchronous function rendering a [Promise]
function async01(débutAsync01) {
return new Promise(function (resolve) {
console.log("[début fonction asynchrone async01],", heure());
// asynchronous function
setTimeout(function () {
console.log("[fin fonction asynchrone async01],", heure(débutAsync01));
// asynchronous action can make a result complex
// here success
resolve({
prop1: [11, 21, 31],
prop2: "abcd"
});
}, 1000)
});
}
// an asynchronous function rendering a [Promise]
function async02(débutAsync02) {
console.log("[début fonction asynchrone async02],", heure());
return new Promise(function (resolve) {
// asynchronous function
setTimeout(function () {
console.log("[fin fonction asynchrone async02],", heure(débutAsync02));
// asynchronous action can make a result complex
// here success
resolve({
prop1: [12, 22, 32],
prop2: "xyzt"
});
}, 2000)
})
}
// an asynchronous function rendering a [Promise]
function async03(débutAsync03) {
console.log("[début fonction asynchrone async03],", heure());
return new Promise((resolve, reject) => {
// asynchronous function
setTimeout(function () {
console.log("[fin fonction asynchrone async03],", heure(débutAsync03));
// asynchronous action can make a result complex
// here failure
reject({
prop1: [13, 23, 33],
prop2: "échec"
});
}, 3000)
})
}
// asynchronous function - using async / await
async function main() {
const débutSequential = moment(Date.now());
// sequential execution of asynchronous tasks
console.log("------------ exécution séquentielle des tâches asynchrones lancée ------------------------")
try {
// execution while waiting for [async01]
const débutAsync01 = moment(Date.now());
const result1 = await async01(débutAsync01);
console.log("[async01 result]=", result1);
// execution while waiting for [async02]
const débutAsync02 = moment(Date.now());
console.log("début async02-------------", heure());
const result2 = await async02(débutAsync02);
console.log("[async02 result]=", result2);
// execution while waiting for [async03]
const débutAsync03 = moment(Date.now());
console.log("début async03-------------", heure());
const result3 = await async03(débutAsync03);
console.log("[async03 result]=", result3);
} catch (error) {
// one of the asynchronous actions has failed
console.log(sprintf("[sequential error]= %j, %s", error, heure(débutSequential)));
} finally {
// completed
console.log("[fin exécution séquentielle des tâches asynchrones],", heure(débutSequential));
}
const débutParallel = moment(Date.now());
// parallel execution of asynchronous tasks
console.log("------------ exécution parallèle des tâches asynchrones lancée ------------------------")
try {
const result = await Promise.all([async01(débutParallel), async02(débutParallel), async03(débutParallel)]);
console.log(sprintf("[parallel success], %s, result=%j", heure(débutParallel), result));
} catch (error) {
// one of the asynchronous actions has failed
console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
} finally {
// completed
console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
}
// completed
console.log("[fin de la fonction main],", heure(débutSequential));
}
// execution asynchronous function main
main();
// will be displayed before the various msgs for asynchronous functions and their events
console.log("[fin du code principal du script],", heure(débutScript));
// utility
function heure(début) {
// current time
const now = moment(Date.now());
// time formatting
let result = "heure=" + now.format("HH:mm:ss:SSS");
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// formatting duration
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// result
return result;
}
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