11. Programación basada en eventos y funciones asincrónicas

Una función asíncrona es aquella cuya ejecución se inicia, pero cuyo resultado no se espera. Cuando finaliza la ejecución, la función asíncrona emite un evento y transmite su resultado a través de él.
Este modo de funcionamiento se adapta bien a la ejecución dentro de un navegador web. De hecho, una aplicación que se ejecuta dentro de un navegador es una aplicación basada en eventos: la aplicación reacciona a eventos, provocados principalmente por el usuario (clics, movimientos del ratón, escritura de texto, etc.). Las aplicaciones de JavaScript que se ejecutan en un navegador deben comunicarse con servicios externos a través del protocolo HTTP. Las funciones nativas de JavaScript HTTP son asincrónicas: se inician y la recepción de la respuesta del servicio externo solicitado se indica mediante un evento que se suma al conjunto de eventos gestionados por la aplicación.
Los siguientes scripts serán ejecutados por [node.js] y no por un navegador. [node.js] también tiene un modo de ejecución por eventos:
- [node.js], al igual que un navegador, utiliza un bucle de eventos para ejecutar un script;
- la ejecución del código principal del script es el primer evento que se ejecuta;
- si este código principal ha iniciado tareas asincrónicas, la ejecución continúa hasta que dichas tareas asincrónicas hayan finalizado. Estas emiten un evento cuando concluyen. Estos eventos se colocan en una cola dentro del bucle de eventos;
- el script principal debe suscribirse a estos eventos si desea recuperar los resultados de las acciones asincrónicas;
- el script no finaliza hasta que se hayan procesado todos los eventos emitidos por él;
11.1. script [async-01]
El siguiente script muestra el comportamiento de un script que incluye una acción asincrónica.
'use strict';
// importaciones
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// Inicio
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// setTimeout activa un temporizador de 1000 ms (segundo parámetro) y devuelve inmediatamente el número de ese temporizador
// cuando el temporizador agota los 1000 ms, emite un evento que se coloca en la fila de espera del tiempo de ejecución
// cuando el entorno de ejecución procesa el evento, se ejecuta la función (primer parámetro)
setTimeout(function () {
// Este código se ejecutará cuando el temporizador alcance el valor 0
console.log("[fin de l'action asynchrone setTimeout],", heure(débutScript));
}, 1000)
// se mostrará antes del mensaje de la función interna del temporizador
console.log("[fin du code principal du script],", heure(débutScript));
// utilidad para mostrar la hora y la duración
function heure(début) {
// hora actual
const now = moment(Date.now());
// formato de la hora
let result = "heure=" + now.format("HH:mm:ss:SSS");
// ¿Se debe calcular una duración?
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// formato de hora + duración
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// resultado
return result;
}
- línea 4: se importa la biblioteca [moment] para poder dar formato a las fechas (línea 27);
- línea 5: se importa la biblioteca [sprintf-js] para poder dar formato a las duraciones (línea 34);
- línea 8: se registra la hora de inicio del script;
- línea 9: se muestra utilizando el método [heure] de las líneas 20 a 34;
- líneas 14-17: la función [setTimeout] tiene dos parámetros (f, duración): f es una función que se ejecuta cuando han transcurrido [durée] milisegundos;
- línea 14: al ejecutar el script, se ejecuta la función [setTimeout]:
- línea 17: se activa un temporizador de 1000 ms y comienza la cuenta regresiva hasta llegar posteriormente a 0. La función [setTimeout] finaliza tan pronto como se inicializa el temporizador y comienza la cuenta regresiva. No espera a que finalice esta cuenta regresiva. Devuelve un número que identifica el temporizador utilizado y la ejecución pasa a la siguiente instrucción, línea 20. Aquí, el resultado de [setTimeout] no se utiliza;
- línea 16: este mensaje se mostrará al finalizar el tiempo de 1000 ms de la función [setTimeout];
- líneas 15-16: la función f, primer parámetro de la función [setTimeout], se ejecutará al finalizar el tiempo de espera de 1000 ms. A continuación, se mostrará el mensaje de la línea 16;
- línea 20: este mensaje se mostrará antes que el de la línea 16;
Función de hora:
- línea 23: la función admite un parámetro opcional [heure], que es la hora de inicio de una operación cuya duración debe mostrar;
- líneas 25-27: se calcula y se formatea la hora actual;
- línea 29: si el parámetro [début] está presente, entonces hay que calcular una duración;
- línea 30: la duración de la operación. Se obtiene un número de milisegundos;
- líneas 31-32: este número de milisegundos se descompone en segundos y milisegundos;
- línea 34: la duración se suma a la hora;
Ejecución
[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
- En la línea 4, se observa que la acción asíncrona [setTimeOut] terminó aproximadamente 1 s después de que finalizara el código principal del script;
- línea 6: la hora que se muestra en la línea 3 es la de la finalización del código principal. Si este ha iniciado tareas asincrónicas, el script no termina hasta que se hayan ejecutado todas las tareas asincrónicas. La duración que se muestra en la línea 6 es la duración total de la ejecución del script (código principal + tareas asincrónicas);
La función [setTimeout] nos permitirá simular tareas asincrónicas en un entorno [node.js]. De hecho, la función [setTimeout] se comporta como una tarea asincrónica:
- devuelve un resultado de inmediato, en este caso un número de temporizador, mediante el mecanismo habitual de las funciones (return);
- puede devolver posteriormente (aunque aún no es el caso anterior) otros resultados a través de eventos que luego son procesados por el bucle de eventos de [node.js];
- en la mayoría de los casos que vendrán a continuación, habrá dos eventos:
- un evento que podríamos llamar [success], el cual será emitido por la tarea asíncrona que completó con éxito lo que debía hacer. Se asocia un dato —el resultado de la tarea— al evento emitido;
- un evento que podríamos llamar [failure], que será emitido por la tarea asincrónica que no logró realizar lo que debía hacer. Un dato —por lo general, un objeto que describe el error— se asocia al evento emitido. Algunos errores posibles, por ejemplo, en una tarea asincrónica de Internet, serían «red no disponible», «servidor inexistente», «tiempo de espera agotado», etc.
- El código principal que inició una tarea asíncrona puede suscribirse a los eventos que dicha tarea pueda emitir. Cuando se emite uno de estos eventos, se notifica al código principal, el cual puede activar la ejecución de una función específica destinada a manejar el evento. Esta función recibe como parámetro el dato que la tarea asíncrona ha asociado al evento emitido;
11.2. script [async-02]
En este script, la función asíncrona [setTimeout] emitirá eventos para comunicar datos a los códigos que se hayan suscrito a ellos.
El acceso a los eventos de [node.js] requiere bibliotecas adicionales. Elegimos la biblioteca [events], que instalamos junto con [npm]:

El script [async-02] es el siguiente:
'use strict';
// las funciones asincrónicas pueden devolver un resultado emitiendo un evento
// el código principal puede recuperar estos resultados suscribiéndose a los eventos emitidos
// importaciones
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';
// comienzo
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// un emisor de eventos
const eventEmitter = new EventEmitter();
// setTimeout activa un temporizador de 1000 ms (segundo parámetro) y devuelve inmediatamente el número de ese temporizador
// cuando el temporizador agota los 1000 ms, emite un evento que se coloca en la fila de espera del tiempo de ejecución
// cuando el entorno de ejecución procesa el evento, se ejecuta la función (primer parámetro)
setTimeout(function () {
// Este código se ejecutará cuando el temporizador alcance el valor 0
console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
// se emite un evento para indicar que hay un resultado disponible
eventEmitter.emit("timer1Success", { success: 4 });
// se emite otro evento para indicar que hay otro resultado disponible
eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)
// se suscribe al 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)));
});
// se suscribe al 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)));
});
// se mostrará antes de los mensajes de los eventos emitidos por la función asociada a [timer1]
console.log("[fin du code principal du script],", heure(débutScript));
// utilidad para mostrar la hora y la duración
function heure(début) {
// hora actual
const now = moment(Date.now());
// formato de la hora
let result = "heure=" + now.format("HH:mm:ss:SSS");
// ¿Se debe calcular una duración?
if (début) {
const durée = now - début;
const milliseconds = durée % 1000;
const seconds = Math.floor(durée / 1000);
// formato de hora + duración
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// resultado
return result;
}
Comentarios
- línea 9: se importa la clase [EventEmitter] de la biblioteca [events]. Esto es una novedad: hasta ahora solo habíamos importado objetos literales y funciones;
- línea 15: se crea un emisor de eventos [node.js] al instanciar la clase [EventEmitter] con la palabra clave [new];
- líneas 20-27: la función asíncrona [setTimeout]. Emitirá dos eventos durante su ejecución:
- línea 24, el evento [timer1Success] con el objeto {success : 4} como valor asociado;
- línea 26, el evento [timer1Failure] con el objeto {failure: 6} como valor asociado;
- una función asíncrona puede generar tantos eventos como desee. Anteriormente se mencionó que, en la mayoría de los casos, genera uno de los dos eventos [success, failure], no ambos como se hace aquí;
- línea 20: la ejecución de [setTimeout] es instantánea: se activa un temporizador y se devuelve su número al código que lo invocó. La emisión de los eventos se realizará más tarde, en este caso 1 segundo después;
- la emisión de eventos es inútil si no hay ningún código para procesarlos cuando se produzcan. Por eso, el código principal debe suscribirse a los dos eventos [timer1Success, timer1Failure] si desea gestionarlos, en particular para recuperar los datos asociados a dichos eventos;
- líneas 30-32: el código principal se suscribe al evento [timer1Success]. Cuando el canal de eventos de [node.js] procese este evento, llamará a la función que constituye el segundo parámetro del método [eventEmitter.on], pasándole los datos (aquí denominados [result]) asociados al evento [timer1Success];
- línea 31: la función de procesamiento del evento mostrará el jSON de los datos asociados al evento, así como la hora actual;
- líneas 35-37: con un código similar, el código principal se suscribe al evento [timer1Failure];
- La suscripción a un evento (primer parámetro) no ejecuta de inmediato el código de la función [callback] (segundo parámetro). Esta función solo se ejecutará después de que el evento haya ocurrido;
- línea 40: el código principal del script ha finalizado, pero no el script en sí, ya que el código principal inició una tarea asíncrona. El script global no terminará hasta que concluya dicha tarea asíncrona;
Esto es lo que muestran los resultados obtenidos:
[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
- línea 3: fin del código principal 10 ms después del inicio del script;
- línea 4: inicio de la función encapsulada en el temporizador de 1000 ms, aproximadamente 1 segundo después del inicio del script;
- línea 5: procesamiento del evento [‘timer1Success’], 2 ms después;
- línea 6: procesamiento del evento [‘timer1Failure’], 1 ms después del evento [‘timer1Success’];
- línea 8: fin del script global con una duración total de 1,627 segundos;
11.3. script [async-03]
El siguiente script muestra otro aspecto del bucle de eventos de [node.js]:
- el bucle ejecuta los eventos uno tras otro, generalmente en el orden en que llegan. Algunos OS asignan prioridades a los eventos, los cuales se procesan entonces por orden de prioridad y no por orden de llegada;
- el bucle ejecuta solo un evento a la vez. El siguiente no se procesa hasta que se haya completado el procesamiento del anterior. En un sistema basado en eventos, por lo tanto, se debe evitar escribir código que monopolice el procesador durante mucho tiempo, ya que en ese caso los eventos no se procesan cuando ocurren, sino más tarde, cuando el bucle de eventos llega a ellos. De este modo, se obtiene una aplicación poco «reactiva»;
El script [async-03] muestra un ejemplo de este fenómeno:
'use strict';
// las funciones asincrónicas pueden devolver un resultado emitiendo un evento
// el código principal puede recuperar estos resultados suscribiéndose a los eventos emitidos
// importaciones
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';
// comienzo
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// un emisor de eventos
const eventEmitter = new EventEmitter();
// setTimeout activa un temporizador de 1000 ms (segundo parámetro) y devuelve inmediatamente el número de ese temporizador
// cuando el temporizador agota los 1000 ms, emite un evento que se coloca en la fila de espera del tiempo de ejecución
// cuando el tiempo de ejecución procesa el evento, se ejecuta la función (primer parámetro)
setTimeout(function () {
//: este código se ejecutará cuando el temporizador llegue al valor 0
console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
// se emite un evento para indicar que hay un resultado disponible
eventEmitter.emit("timer1Success", { success: 4 });
// se emite otro evento para indicar que hay otro resultado disponible
eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)
// se suscribe al 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)));
});
// se suscribe al 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 código síncrono un poco intensivo que impidió que el código principal terminara antes de que concluyera [timer1]
for (let i = 0; i < 1000000; i++) {
for (let j = 0; j < 10000; j++) {
i + i ^ 2 + i ^ 3;
}
}
// se mostrará antes de los mensajes de los eventos emitidos por la función asociada a [timer1]
console.log("[fin du script],", heure(débutScript));
// utilidad para mostrar la hora y la duración
function heure(début) {
...
}
Comentarios
- Este código es el del ejemplo anterior [async-02], al que se le han agregado las líneas 39 a 44;
- líneas 20-27: la función [setTimeout] se ha programado para ejecutar una función asíncrona interna tras un retraso de un segundo. Al cabo de ese segundo, la ejecución de la función asíncrona del temporizador no ocurre de inmediato: se inserta un evento en el bucle de ejecución para solicitarla. Si el bucle de ejecución está ocupado procesando otro evento, la ejecución de la función asíncrona del temporizador deberá esperar;
- líneas 20-27: tan pronto como la función [setTimeout] ha activado su temporizador con un tiempo de espera de un segundo, libera al procesador y devuelve el control al código que la invocó. Este continúa con las líneas 30-37, que son suscripciones a eventos y tienen un tiempo de ejecución insignificante;
- el código principal continúa con las líneas 40-44, que forman un bucle de 1010 iteraciones. Este código estará en ejecución cuando el temporizador emita su evento de «fin del lapso de 1 segundo». Este evento se coloca entonces en el bucle de eventos, pero deberá esperar a que finalice la ejecución del código principal del script para tener la oportunidad de ser procesado;
- línea 47: fin del código principal del script. Es después de esta última visualización cuando el evento de fin del temporizador podrá ser procesado y la función asíncrona interna de [setTimeout] podrá ejecutarse;
El script arroja los siguientes resultados:
[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
Comentarios
- línea 3: se observa que el código principal del script tardó 9 segundos en ejecutarse. Los eventos que pudieron ocurrir durante ese tiempo quedaron en espera en el bucle de eventos;
- línea 4: se observa que el evento [fin du timer] se procesó 5 ms después de que finalizara el código principal. Se emitió aproximadamente 1 s después del inicio del script, pero tuvo que esperar 8 s adicionales para ser procesado finalmente;
De este ejemplo se desprende que, en un sistema basado en eventos, un código nunca debe ocupar el procesador durante mucho tiempo. Si tenemos un código síncrono que tarda mucho en ejecutarse, debemos «lasar de arreglárnoslas» para descomponerlo en tareas asíncronas más cortas que señalarán su final con un evento.
11.4. script [async-04]
El script [async-04] muestra otro mecanismo, llamado [Promise], una promesa de resultado. Este mecanismo evita tener que gestionar explícitamente los eventos [node.js]. Esto se hace de manera implícita y, por lo tanto, el desarrollador puede ignorar la existencia de estos eventos. Sin embargo, comprenderlos le permitirá entender mejor el funcionamiento de los [Promise], que a primera vista resulta complejo.
El tipo [Promise] es una clase de JavaScript. Su constructor admite como parámetro una función asíncrona a la que le pasa dos parámetros denominados tradicionalmente [resolve] y [reject]. Podrían tener otro nombre;
- el constructor de [Promise] hace dos cosas:
- crea un evento para iniciar la ejecución de la función [function(resolve, reject)] que se le pasó como parámetro, pero no espera su resultado y devuelve inmediatamente un objeto [Promise] al código que lo invocó. Este objeto puede tener cuatro estados:
- [pending]: la acción asíncrona que generó la [Promise] aún no ha finalizado;
- [fulfilled]: la acción asíncrona que generó la [Promise] ha finalizado con éxito;
- [rejected]: la acción asíncrona que generó la [Promise] terminó con un error;
- [settled]: la acción asíncrona que generó la [Promise] ha finalizado;
- crea un evento para iniciar la ejecución de la función [function(resolve, reject)] que se le pasó como parámetro, pero no espera su resultado y devuelve inmediatamente un objeto [Promise] al código que lo invocó. Este objeto puede tener cuatro estados:
Cuando el constructor devuelve su resultado, el objeto [Promise] creado se encuentra en el estado [pending], a la espera de los resultados de la función asíncrona;
- (continuación)
- la tarea asíncrona de las líneas 2 a 5 se inicia de inmediato. Las tareas asíncronas suelen ser tareas de entrada/salida asíncronas que se desglosan de la siguiente manera:
- ejecución de un código síncrono para iniciar la operación de E/S con otro componente, por ejemplo, un servidor remoto;
- espera de la respuesta de ese componente;
- procesamiento de dicha respuesta;
- la tarea asíncrona de las líneas 2 a 5 se inicia de inmediato. Las tareas asíncronas suelen ser tareas de entrada/salida asíncronas que se desglosan de la siguiente manera:
La fase 2, de espera del componente externo al procesador, es la más costosa. En lugar de esperar:
- la recepción de los datos solicitados al componente externo se indicará mediante un evento;
- en el código síncrono que seguirá a la fase 1 (línea 7 del código de ejemplo), nos suscribiremos a este evento y luego, en algún momento, regresaremos al bucle de eventos de [node.js]. A continuación, se procesará el siguiente evento de la lista de eventos en espera;
- durante la fase 2, existe paralelismo de ejecución, pero en dispositivos diferentes:
- el procesador para el bucle de eventos;
- un dispositivo externo (disco, base de datos, servidor remoto) para la búsqueda de los datos solicitados;
- al final de la fase 2, cuando la operación de E/S haya obtenido los datos solicitados, se emitirá un evento para indicar que el resultado de la E/S está disponible. Este evento se sumará entonces a los demás en la lista de espera de eventos;
- cuando llegue su turno, se procesará. La función asociada a este evento (línea 7 del código de ejemplo) se ejecutará entonces;
Este modo de funcionamiento permite evitar los tiempos muertos: aquellos en los que el procesador espera la respuesta de un dispositivo más lento que él;
- (continuación)
- cuando la tarea asíncrona de las líneas 2 y 5 se ha iniciado y ha terminado su trabajo, tiene la posibilidad de devolver un resultado al código que la invocó, gracias a las dos funciones [resolve, reject] que el constructor [Promise] le pasó como parámetros. La convención es la siguiente:
- la tarea asíncrona indica que se ha realizado con éxito mediante [resolve(result)]. Esto equivale a incluir en el bucle de eventos de [node.js] un evento que podríamos llamar [resolved] con [result] como dato asociado;
- la tarea asíncrona reporta un error mediante [reject(error)]. Esto equivale a incluir en el ciclo de eventos de [node.js] un evento que podríamos llamar [rejected] con [error] como dato asociado, por lo general un objeto que detalla el error que se produjo;
- por lo tanto, el código que realiza la llamada debe suscribirse a estos dos eventos para recibir una notificación cuando el resultado de la función asíncrona esté disponible;
- cuando la tarea asíncrona de las líneas 2 y 5 se ha iniciado y ha terminado su trabajo, tiene la posibilidad de devolver un resultado al código que la invocó, gracias a las dos funciones [resolve, reject] que el constructor [Promise] le pasó como parámetros. La convención es la siguiente:
Una vez finalizada la ejecución de la tarea asíncrona encapsulada en [Promise], cambia el estado del objeto [promise] devuelto por el constructor [Promise(…)]:
- el evento [resolved] lo hace pasar del estado [pending] al estado [resolved];
- el evento [rejected] lo hace pasar del estado [pending] al estado [rejected];
La suscripción a los eventos [resolved] y [rejected] de la tarea asíncrona se realiza mediante métodos de la clase [Promise] con la siguiente sintaxis:
promise.then(f1).catch(f2).finally(f3);
donde:
- f1 es una función que se ejecuta cuando el estado de [promise] pasa de [pending] a [resolved], es decir, cuando la tarea asíncrona ha completado con éxito su trabajo. Recibe como parámetro el valor [result], transmitido por la instrucción [resolve(result)] de la tarea asíncrona;
- f2 es una función que se ejecuta cuando el estado de [promise] cambia de [pending] a [rejected], es decir, cuando la tarea asíncrona no ha podido completar su trabajo. Recibe como parámetro el valor [error], transmitido por la instrucción [reject(errror)] de la tarea asincrónica;
- f3 es una función que se ejecuta después de que se ejecuten los métodos [then] o [catch], por lo que siempre se ejecuta. No recibe ningún parámetro;
Esta sintaxis oculta por completo los eventos a los que nos suscribimos. Sin embargo, se trata de una suscripción y, al igual que en el ejemplo anterior, no ejecuta inmediatamente las funciones [f1, f2, f3]. Estas se ejecutarán o no cuando ocurra uno de los eventos [resolved, rejected] a los que nos suscribimos.
El script [async-04] ilustra este mecanismo:
'use strict';
// es posible obtener los resultados (éxito, fracaso) de una función asíncrona
// sin utilizar eventos de manera explícita gracias a la clase [Promise]
// esta clase utiliza eventos de manera implícita, pero estos no se ven en el código
// importaciones
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// comienzo
const débutScript = moment(Date.now());
console.log("[début du script],", heure(débutScript));
// definición de una tarea asíncrona mediante una promesa [Promise]
// la tarea asíncrona es el parámetro del constructor [Promise]
const débutPromise1 = moment(Date.now());
const promise1 = new Promise(function (resolve) {
// registro
console.log("[début fonction asynchrone de promise1],", heure(débutPromise1));
// código asíncrono
setTimeout(function () {
console.log("[fin fonction asynchrone de promise1],", heure(débutPromise1));
// la tarea asíncrona devuelve un resultado con la función [resolve]
// la promesa se cumple entonces
resolve('[réussite]');
}, 1000)
});
// se puede conocer el resultado de la promesa [promise1]
// cuando esta se ha resuelto (resolve) o rechazado (reject)
// la instrucción siguiente es una suscripción al evento [resolved] mediante el método [then]
// y al evento [rejected] mediante el método [catch]
// el método [finally] se ejecuta ya sea después de un «then» o de un «catch»
promise1.then(result => {
// en caso de que la promesa se cumpla [evt resolved]
console.log(sprintf("[promise1.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
// caso de error [evt rejected]
console.log(sprintf("[promise1.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
// ejecutado en todos los casos
console.log("[promise1.finally]", heure(débutPromise1));
});
// definición de una tarea asíncrona mediante una promesa [Promise]
const débutPromise2 = moment(Date.now());
const promise2 = new Promise(function (resolve, reject) {
// registro
console.log("[début fonction asynchrone de promise2],", heure(débutPromise1));
// tarea asíncrona
setTimeout(function () {
console.log("[fin fonction asynchrone de promise2],", heure(débutPromise2));
// la tarea asíncrona devuelve un resultado con la función [reject]
// la promesa falla entonces
reject('[échec]');
}, 2000)
});
// se puede conocer el resultado de la promesa [promise2]
// una vez que esta se ha resuelto (resolve) o rechazado (reject)
promise2.then(result => {
// en caso de que la promesa se cumpla [evt resolved]
console.log(sprintf("[promise2.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
// en caso de error [evt rejected]
console.log(sprintf("[promise2.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
// se ejecuta en todos los casos
console.log(sprintf("[promise2.finally], %s", heure(débutPromise2)));
});
// se mostrará antes de los mensajes de las funciones asincrónicas y de los eventos asociados
console.log("[fin du code principal du script],", heure(débutScript));
// utilidad
function heure(début) {
// hora actual
const now = moment(Date.now());
// formato de la hora
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);
// formato de duración
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// resultado
return result;
}
Comentarios
- líneas 18-28: creación de un [Promise promise1]. Su función asíncrona devuelve su resultado mediante un evento al cabo de un segundo. Una vez iniciada esta operación asíncrona (activación de un temporizador), no se espera a que devuelva su resultado y se pasa inmediatamente al código de la línea 35;
- líneas 35-44: nos suscribimos a los dos eventos [resolved, rejected] que puede emitir la función asíncrona interna de [promise1];
- líneas 46-71: se repite la misma secuencia de código que antes para una segunda promesa [promise2];
- línea 74: el código principal del script ha finalizado, pero no el script en su totalidad, ya que se han iniciado dos acciones asincrónicas. Se regresa al bucle de eventos, donde en algún momento ocurrirá uno de los eventos [resolved, rejected] de las promesas [promise1, promise2]. En ese momento se procesará;
- luego se regresará al bucle de eventos. Y ahí se procesará el segundo evento [resolved, rejected] de las promesas [promise1, promise2] cuando ocurra;
Ejecución
[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
Comentarios
- línea 3: se inicia la función asíncrona de [promise1], pero no se espera a que termine, lo cual se señalará mediante un evento;
- línea 4: se inicia la función asíncrona de [promise2], pero no se espera a que termine, lo cual se indicará mediante un evento;
- línea 5: fin del código principal y regreso al bucle de eventos;
- línea 6: procesamiento del evento [fin fonction asynchrone de promise1]. El estado de [promise1] pasará a ser [resolved]. Un evento lo señala;
- línea 7: dado que [promise2] aún no ha terminado su trabajo, el evento [promise1 resolved], que acaba de ingresarse al ciclo, será procesado por el método [promise1.then] y luego por el método [promise.finally] (línea 8);
- líneas 9-11: el mismo mecanismo se repite cuando [promise2] pasa del estado [pending] al estado [resolved];
11.5. script [async-05]
Volvamos al código del constructor de un objeto [Promise]:
En la línea 2, se inicia la tarea asíncrona de [Promise]. A menudo necesita más parámetros que los únicos que le pasa la función que la encapsula, [resolve, reject]. En este caso, se encapsula la creación de [Promise] en una función que le pasará los parámetros que necesita su función asíncrona:
El siguiente script:
- define dos funciones asincrónicas que devuelven un [Promise];
- inicia su ejecución en paralelo y espera a que ambas hayan finalizado para realizar una tarea determinada;
'use strict';
// Se pueden definir funciones asincrónicas que devuelvan un tipo [Promise]
// y, por lo tanto, se pueden etiquetar con la palabra clave [async]
// importaciones
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// inicio
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// una función asíncrona que devuelve una promesa [Promise]
function async01(p1) {
return new Promise((resolve) => {
console.log("[début de la tâche asynchrone async01]");
// la tarea asíncrona
const débutAsync01 = moment(Date.now());
setTimeout(function () {
console.log("[fin de la tâche asynchrone async01],", heure(débutAsync01));
// la tarea asíncrona puede devolver un resultado complejo
resolve({
prop1: [10, 20, 30],
prop2: "abcd",
prop3: p1,
});
}, 1000)
});
}
// una función asíncrona que devuelve una promesa [Promise]
function async02(p1, p2) {
return new Promise(resolve => {
console.log("[début de la tâche asynchrone async02]");
// tarea asíncrona
const débutAsync02 = moment(Date.now());
setTimeout(function () {
console.log("[fin de la tâche asynchrone async02],", heure(débutAsync02));
// la tarea asíncrona puede devolver un resultado complejo
resolve({
prop1: [11, 21, 31],
prop2: "xyzt",
prop3: p1 + p2
});
}, 2000)
})
}
// se ejecutan las dos funciones asincrónicas en paralelo
// y se espera a que ambas hayan terminado
// el then solo se ejecuta si ambas funciones han emitido el evento [resolved]
// el «catch» se ejecuta tan pronto como una de las dos funciones emita el evento [rejected]
Promise.all([async01(10), async02(10, 20)])
//; el resultado es una matriz [result1, result2], donde [result1] es el resultado emitido por un [resolve] de [async01]
// y [result2] es el resultado emitido por un [resolve] de [async02]
.then(result => {
console.log(sprintf("[promise-all success], %s, result=%j", heure(débutScript), result));
})
// error es el resultado emitido por el primer [reject] de una de las dos funciones asincrónicas
.catch(error => {
console.log(sprintf("[promise-all error], %s, erreur=%j", heure(débutScript), error));
})
// finally se ejecuta después de then o catch
.finally(() => {
console.log(sprintf("[promise-all finally], %s", heure(débutScript)));
});
// se mostrará antes de los mensajes de las funciones asincrónicas y de los eventos asociados
console.log("[fin du code principal du script],", heure(débutScript));
// utilidad
function heure(début) {
// hora actual
const now = moment(Date.now());
// formato de hora
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);
// formato de duración
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// resultado
return result;
}
Comentarios
- líneas 15-30: se define una función [async01] que devuelve su resultado al cabo de 1 segundo mediante un evento de temporizador. La función [async01], cuyo resultado se utiliza en la línea 26;
- líneas 33-47: se hace lo mismo con una función [async02] que devuelve su resultado al cabo de 2 segundos mediante un evento de temporizador. La función [async02] admite dos parámetros que se utilizan en su resultado en la línea 44;
- cuando se llamen las dos funciones [async01, async02]:
- se ejecutarán;
- entregarán al código que las invocó dos promesas [promise1, promise2];
- la ejecución volverá entonces al código que las invocó, el cual continuará su ejecución;
- al cabo de aproximadamente 1 segundo, [async01] emitirá un evento para indicar que ha terminado su trabajo. El evento en cuestión quedará en espera en el bucle de eventos asociado al resultado transmitido por [async01] junto con el evento;
- al cabo de aproximadamente 2 segundos, ocurrirá lo mismo con [async02];
- línea 54: solo ahora se ejecutan las funciones asíncronas [async01, async02] (notaciones async01(10) y async02(10,20)). Se ejecutan dentro de una matriz pasada como parámetro al método [Promise.all]. Sabemos que [async01, async02] devuelven ambas una promesa al código que las invoca. Por lo tanto, el parámetro de [Promise.all] es una matriz de dos promesas;
- [Promise.all([promise1, promise2, …, promisen]).then(f1).catch(f2).finally(f3)] es una suscripción a eventos:
- [Promise.all] es de tipo [Promise];
- la función [f1] del método [then] se ejecutará cuando todas las promesas [promise1, promise2, …, promisen] del array de parámetros del método [all] hayan pasado delestado [pending] al estado [resolved]. Dicho de otra manera, [f1] se ejecutará cuando todas las promesas de la matriz hayan finalizado con éxito;
- la función [f2] del método [catch] se ejecutará tan pronto como una de las promesas de la matriz pase del estado [pending] al estado [rejected]. En otras palabras, [f2] se ejecuta tan pronto como falle una de las promesas de la matriz;
- la función [f3] del método [finally] se ejecutará después de que se ejecute uno de los métodos [then, catch], por lo que siempre se ejecutará;
La ejecución del código arroja los siguientes resultados:
[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
- líneas 6-7: se inician las dos tareas asincrónicas [async01, async02]. Funcionan en paralelo. No es la ejecución de su código lo que se realiza en paralelo, sino que sus respectivas esperas por los datos solicitados ocurren al mismo tiempo;
- línea 5: el código principal del script ha finalizado. Queda por esperar a que terminen las dos tareas asincrónicas [async01, async02];
- línea 6: la tarea asincrónica [async01] finaliza aproximadamente 1 s después de su inicio. Devuelve un resultado mediante la función [resolve], por lo que su promesa en la matriz de la línea 56 del código pasa del estado [pending] a [resolved]. Esto no es suficiente para activar el método [then], líneas 59-60 del código;
- línea 7: la tarea asíncrona [async02] finaliza aproximadamente 2 s después de su inicio. Devuelve un resultado mediante la función [resolve], por lo que su promesa en la matriz de la línea 56 del código pasa del estado [pending] a [resolved]. El método [then] se ejecutará tan pronto como el bucle de eventos lo permita;
- línea 8: se ejecuta el método [then] de [Promise.all]. Recibe como parámetro un array [result1, result2], donde [result1] es el resultado generado por [async01], y [result2] es el resultado generado por [async02];
- línea 9: se ejecuta el método [finally] de [Promise.all];
11.6. script [async-06]
Este nuevo script muestra cómo el uso conjunto de las palabras clave [async / await] permite obtener un código asíncrono que se asemeja a uno síncrono. La gestión de eventos queda completamente oculta y se facilita la comprensión del código.
Retomamos el ejemplo anterior y le hacemos las siguientes modificaciones:
- se agrega una tercera función asíncrona [async03] que devuelve su resultado mediante el método [Promise.reject], el cual, a su vez, le indica al bucle de eventos que «no logró» realizar su tarea;
- ejecutamos secuencialmente las tres funciones asincrónicas [async01, async02, async03]. En el ejemplo anterior, habíamos ejecutado en paralelo las funciones asincrónicas [async01, async02];
- antes de la aparición de las palabras clave [async/await], la ejecución secuencial de acciones asincrónicas se realizaba mediante funciones [Promise] anidadas unas dentro de otras. Tan pronto como había varias acciones asincrónicas que ejecutar de esta manera, el número de promesas aumentaba en consecuencia y el código se volvía menos legible;
- con las palabras clave [async/await], la ejecución secuencial de tareas asincrónicas se realiza con una sintaxis similar a la de la ejecución de tareas sincrónicas:
// función asíncrona: uso de async/await
async function main() {
// ejecución secuencial de tareas asíncronas
try {
// ejecución en espera de [async01]
const result1 = await async01(...);
console.log("[async01 result]=", result1);
// ejecución con espera de [async02]
const result2 = await async02(...);
console.log("[async02 result]=", result2);
// ejecución en espera de [async03]
const result3 = await async03(...);
console.log("[async03 result]=", result3);
} catch (error) {
// una de las acciones asincrónicas falló
console.log(sprintf("[sequential error]= %j, %s", error));
} finally {
// finalizado
console.log("[fin exécution séquentielle des tâches asynchrones],");
}
- línea 6: se inicia la función asíncrona [async01] (palabra clave await) y se espera a que publique su resultado mediante uno de los métodos [Promise.resolve, Promise.reject]. Por lo tanto, se trata de una operación bloqueante;
- línea 6: la palabra clave [await] convierte la operación asíncrona [async01] en una operación bloqueante. Sabemos que la operación [async01] devuelve un resultado de dos maneras:
- devuelve al código que la invoca, casi de inmediato, un objeto [Promise];
- posteriormente publica un resultado en el bucle de eventos mediante los métodos [Promise.resolve, Promise.reject]. Es este último resultado el que recupera [result1], en la línea 6. La gestión de eventos de la acción [async01] se ha vuelto invisible;
- si el resultado [result] de [async01] es publicado por [Promise.resolve(result)], se asigna a [result1] en la línea 6 y la ejecución continúa en la línea 7;
- Si el resultado de [async01] es publicado por [Promise.reject], esto provoca una excepción y la ejecución del código pasa a la línea 14, la del bloque `catch`. El parámetro de la cláusula [catch] es el objeto de error (error) publicado por [async01] con una expresión [Promise.reject(error)]. La tarea asíncrona también puede publicar el error mediante un [throw(error)]. El objeto [error] es el que se recupera en [catch(error)];
- la palabra clave [await] debe aparecer obligatoriamente en una función precedida por la palabra clave [async], en la línea 2. Esta palabra clave indica que la función [main] es una función asíncrona;
- en la expresión [await f(…)], [f] debe ser una función asíncrona que devuelva un objeto [Promise] al código que la invoca;
- se repite lo mismo para la acción asíncrona [async02], línea 9, y [async03], línea 12;
Siempre con las palabras clave [async / await], es posible ejecutar tareas asincrónicas en paralelo con la siguiente sintaxis:
try {
// ejecución paralela de las tareas asincrónicas
const result = await Promise.all([async01(...), async02(...), async03(...)]);
console.log(sprintf("[parallel success], %s, result=%j", heure(débutParallel), result));
} catch (error) {
// una de las acciones asincrónicas falló
console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
} finally {
// finalizado
console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
}
- línea 3: tenemos una operación bloqueante: esperamos a que las tres tareas asincrónicas de la matriz [async01(..), async02(..), async03(..)] hayan publicado sus resultados en el bucle de eventos mediante uno de los métodos [Promise.resolve, Promise.reject];
- si las tres tareas asincrónicas publican sus resultados con [Promise.resolve], la constante [result] es entonces la matriz [result1, result2, result3], donde:
- [result1] es el resultado publicado por [async01] con la expresión [Promise.resolve(result1)];
- [result2] es el resultado publicado por [async02] con la expresión [Promise.resolve(result2)];
- [result3] es el resultado publicado por [async03] con la expresión [Promise.resolve(result3)];
- si alguna de las tres tareas publica su resultado con una expresión [Promise.reject(error)], se produce una excepción;
- la constante [result] de la línea 3 no recibe su valor;
- la ejecución pasa directamente a [catch] de la línea 5;
- el parámetro (error) del catch es el objeto (error) publicado por la expresión [Promise.reject(error)];
Al combinar estas dos sintaxis, se pueden ejecutar tareas asincrónicas tanto de manera secuencial como en paralelo, todo ello con una sintaxis similar a la de un código síncrono. Por lo tanto, se debe dar preferencia a esta sintaxis, que es mucho más legible que las anteriores. Esta sintaxis [async / await] solo está disponible a partir de la versión 6 de ECMAScript. Todavía hay mucho código JavaScript que utiliza promesas [Promise]. Por eso es importante comprender también cómo funcionan estas.
El código completo del script [async-06] es el siguiente:
'use strict';
// ejecución paralela o secuencial de varias tareas asincrónicas
// con las palabras clave async / await
// importaciones
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// inicio
const débutScript = moment(Date.now());
console.log("[début du code principal du script],", heure());
// una función asíncrona que devuelve un [Promise]
function async01(débutAsync01) {
return new Promise(function (resolve) {
console.log("[début fonction asynchrone async01],", heure());
// función asíncrona
setTimeout(function () {
console.log("[fin fonction asynchrone async01],", heure(débutAsync01));
// la acción asíncrona puede devolver un resultado complejo
// aquí se ha realizado con éxito
resolve({
prop1: [11, 21, 31],
prop2: "abcd"
});
}, 1000)
});
}
// una función asincrónica que devuelve un [Promise]
function async02(débutAsync02) {
console.log("[début fonction asynchrone async02],", heure());
return new Promise(function (resolve) {
// función asíncrona
setTimeout(function () {
console.log("[fin fonction asynchrone async02],", heure(débutAsync02));
// la acción asíncrona puede generar un resultado complejo
// aquí se ha realizado con éxito
resolve({
prop1: [12, 22, 32],
prop2: "xyzt"
});
}, 2000)
})
}
// una función asíncrona que devuelve un [Promise]
function async03(débutAsync03) {
console.log("[début fonction asynchrone async03],", heure());
return new Promise((resolve, reject) => {
// función asíncrona
setTimeout(function () {
console.log("[fin fonction asynchrone async03],", heure(débutAsync03));
// la acción asíncrona puede generar un resultado complejo
// aquí hay un error
reject({
prop1: [13, 23, 33],
prop2: "échec"
});
}, 3000)
})
}
// función asíncrona: uso de async / await
async function main() {
const débutSequential = moment(Date.now());
// ejecución secuencial de tareas asíncronas
console.log("------------ exécution séquentielle des tâches asynchrones lancée ------------------------")
try {
// ejecución en espera de [async01]
const débutAsync01 = moment(Date.now());
const result1 = await async01(débutAsync01);
console.log("[async01 result]=", result1);
// ejecución con espera de [async02]
const débutAsync02 = moment(Date.now());
console.log("début async02-------------", heure());
const result2 = await async02(débutAsync02);
console.log("[async02 result]=", result2);
// ejecución en espera de [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 de las acciones asincrónicas falló
console.log(sprintf("[sequential error]= %j, %s", error, heure(débutSequential)));
} finally {
// finalizado
console.log("[fin exécution séquentielle des tâches asynchrones],", heure(débutSequential));
}
const débutParallel = moment(Date.now());
// ejecución en paralelo de las tareas asincrónicas
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 de las acciones asincrónicas falló
console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
} finally {
// finalizado
console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
}
// finalizado
console.log("[fin de la fonction main],", heure(débutSequential));
}
// ejecución de la función asíncrona main
main();
// aparecerá antes de los distintos mensajes de las funciones asincrónicas y sus eventos
console.log("[fin du code principal du script],", heure(débutScript));
// utilidad
function heure(début) {
// hora actual
const now = moment(Date.now());
// formato de la hora
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);
// formato de duración
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// resultado
return result;
}
Los resultados de la ejecución son los siguientes:
[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