Skip to content

11. Programación basada en eventos y funciones asíncronas

Image

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 en un navegador web. De hecho, una aplicación que se ejecuta en 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, introducción 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 HTTP de JavaScript son asíncronas: se inician y la recepción de la respuesta del servicio externo solicitado se señala mediante un evento que se añade 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 asíncronas, la ejecución continúa hasta que dichas tareas asíncronas hayan finalizado. Estas emiten un evento cuando finalizan. Estos eventos se colocan en cola en el bucle de eventos;
  • el script principal debe suscribirse a estos eventos si desea recuperar los resultados de las acciones asíncronas;
  • el script solo finaliza cuando se han 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 asíncrona.


'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 dicho temporizador
// cuando el temporizador ha agotado los 1000 ms, emite un evento que se coloca en la cola 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");
  // ¿Hay que 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 anota la hora de inicio del script;
  • línea 9: se muestra mediante el método [heure] de las líneas 20-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 atrás hasta llegar posteriormente a 0. La función [setTimeout] finaliza en cuanto se inicializa el temporizador y comienza la cuenta atrás. No espera a que finalice dicha cuenta atrás. Devuelve un número que identifica el temporizador utilizado y la ejecución pasa a la siguiente instrucción, la línea 20. Aquí, el resultado de [setTimeout] no se utiliza;
  • línea 16: este mensaje se mostrará al finalizar el plazo 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, 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] finalizó aproximadamente 1 s después de que terminara el código principal del script;
  • línea 6: la hora que aparece en la línea 3 es la de la finalización del código principal. Si este ha iniciado tareas asíncronas, el script no termina hasta que se hayan ejecutado todas las tareas asíncronas. La duración que aparece en la línea 6 es la duración total de la ejecución del script (código principal + tareas asíncronas);

La función [setTimeout] nos permitirá simular tareas asíncronas en un entorno [node.js]. De hecho, la función [setTimeout] se comporta como una tarea asíncrona:

  • devuelve un resultado de forma inmediata —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 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 denominar [success], que será emitido por la tarea asíncrona que ha completado con éxito lo que debía hacer. Al evento emitido se le asocia un dato: el resultado de la tarea;
    • un evento que podríamos denominar [failure], que será emitido por la tarea asíncrona que no ha logrado realizar lo que debía. Al evento emitido se le asocia un dato, generalmente un objeto que describe el error. Algunos posibles errores, por ejemplo, en una tarea asíncrona de Internet, serían «red no disponible», «servidor inexistente», «tiempo de espera agotado», etc.
  • El código principal que ha iniciado una tarea asíncrona puede suscribirse a los eventos que dicha tarea pueda emitir. Cuando se emite uno de ellos, se avisa al código principal, que puede activar la ejecución de una función específica destinada a gestionar 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]:

Image

El script [async-02] es el siguiente:


'use strict';

// las funciones asíncronas 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';

// Inicio
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 dicho temporizador
// cuando el temporizador ha agotado los 1000 ms, emite un evento que se coloca en la cola 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");
  // ¿Hay que 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

  • en la línea 9, se importa la clase [EventEmitter] de la biblioteca [events]. Se trata de una novedad: hasta ahora solo habíamos importado objetos literales y funciones;
  • línea 15: se crea un emisor de eventos [node.js] instanciando 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 emitir tantos eventos como desee. Anteriormente se ha mencionado que, en la mayoría de los casos, emite 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 invoca. La emisión de los eventos se producirá más tarde, en este caso un segundo después;
  • la emisión de eventos es inútil si no hay ningún código que los aproveche cuando se produzcan. Por eso, el código principal debe suscribirse a los dos eventos [timer1Success, timer1Failure] si quiere 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 gestión 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 inmediatamente el código de la función [callback] (segundo parámetro). Esta solo se ejecutará una vez que el evento haya tenido lugar;
  • línea 40: el código principal del script ha finalizado, pero no el script en sí, ya que el código principal ha iniciado una tarea asíncrona. El script global no finalizará hasta que haya concluido 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 más tarde;
  • 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, que entonces se procesan por orden de prioridad y no por orden de llegada;
  • el bucle solo ejecuta un evento a la vez. El siguiente no se procesa hasta que haya finalizado el procesamiento del anterior. En un sistema basado en eventos, por lo tanto, hay que evitar escribir código que monopolice el procesador durante mucho tiempo, ya que, en ese caso, los eventos no se procesan cuando se producen, sino más tarde, cuando el bucle de eventos llega a ellos. El resultado es una aplicación poco «reactiva»;

El script [async-03] muestra un ejemplo de este fenómeno:


'use strict';

// las funciones asíncronas 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';

// Inicio
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 dicho temporizador
// cuando el temporizador ha agotado los 1000 ms, emite un evento que se coloca en la cola 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 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)));
});

// un código síncrono algo intensivo que ha impedido que el código principal finalizara antes de que terminara [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 han añadido las líneas 39-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. Transcurrido ese segundo, la ejecución de la función asíncrona del temporizador no se produce 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: en cuanto la función [setTimeout] ha activado su temporizador con un retraso de un segundo, libera el procesador y devuelve el control al código llamante. Este continúa con las líneas 30-37, que son suscripciones a eventos y cuyo tiempo de ejecución es 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 plazo de 1 segundo». Este evento se introduce 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 tras esta última visualización cuando se podrá procesar el evento de fin del temporizador y se podrá ejecutar la función asíncrona interna de [setTimeout];

El script ofrece 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 producirse 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 más 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 se tiene un código síncrono cuya ejecución lleva mucho tiempo, hay que «apañárselas» para descomponerlo en tareas asíncronas más cortas que señalarán su final mediante un evento.

11.4. script [async-04]

El script [async-04] muestra otro mecanismo, denominado [Promise], una promesa de resultado. Este mecanismo evita tener que gestionar explícitamente los eventos [node.js]. Se hace de forma implícita, por lo que el desarrollador puede ignorar la existencia de dichos 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 pasa dos parámetros denominados tradicionalmente [resolve] y [reject]. Podrían tener otro nombre;

1
2
3
4
5
6
7
const promise=new Promise(function(resolve, reject){
     // se inicia una tarea asíncrona
    
     // si se realiza con éxito: llamar a resolve(result), donde [result] es el resultado de la tarea asíncrona;
     // si falla: llamar a reject(error), donde [error] es un objeto que encapsula el error producido;
}
// suscribirse a los eventos emitidos por la tarea asíncrona de [Promise]
  • 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 ha pasado como parámetro, pero no espera su resultado y devuelve inmediatamente un objeto [Promise] al código que lo ha llamado. Este 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] ha finalizado con un error;
      • [settled]: la acción asíncrona que devolvió [Promise] ha finalizado;

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-5 se inicia inmediatamente. Las tareas asíncronas suelen ser tareas de entrada/salida asíncronas que se desglosan de la siguiente manera:
      1. ejecución de un código síncrono para iniciar la operación de E/S con otro dispositivo, por ejemplo, un servidor remoto;
      2. espera de la respuesta de dicho dispositivo;
      3. procesamiento de dicha respuesta;

La fase 2, de espera del dispositivo externo al procesador, es la más costosa. En lugar de esperar:

  • la recepción de los datos solicitados al dispositivo externo se señalará 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, en un momento dado, volveremos 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 unirá entonces a los demás en la lista de espera de eventos;
  • cuando le llegue el turno, se procesará. A continuación, se ejecutará la función asociada a este evento (línea 7 del código de ejemplo);

Este modo de funcionamiento permite evitar los tiempos muertos: aquellos en los que el procesador espera la respuesta de un periférico más lento que él;

  • (continuación)
    • cuando la tarea asíncrona de las líneas 2 y 5 se ha iniciado y ha finalizado su trabajo, tiene la posibilidad de devolver un resultado al código que la ha llamado, gracias a las dos funciones [resolve, reject] que el constructor [Promise] le ha pasado como parámetros. La convención es la siguiente:
      • la tarea asíncrona indica que se ha completado con éxito mediante [resolve(result)]. Esto equivale a incluir en el bucle de eventos de [node.js] un evento que podríamos denominar [resolved] con [result] como dato asociado;
      • la tarea asíncrona notifica un fallo mediante [reject(error)]. Esto equivale a introducir en el bucle de eventos de [node.js] un evento que podríamos denominar [rejected], con [error] como dato asociado, que suele ser un objeto que detalla el error que se ha producido;
      • 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;

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 [resolved];
  • el evento [rejected] lo hace pasar del estado [pending] al [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] pasa 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 asíncrona;
  • f3 es una función que se ejecuta tras la ejecución de los métodos [then] o [catch], por lo que se ejecuta siempre. 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 se produzca 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, fallo) de una función asíncrona
// sin utilizar eventos de forma explícita gracias a la clase [Promise]
// esta clase utiliza eventos de forma implícita, pero estos no se aprecian en el código

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

// Inicio
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 mediante 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 tanto después de un «then» como 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 mediante la función [reject]
    // la promesa se da por fallida
    reject('[échec]');
  }, 2000)
});

// se puede conocer el resultado de la promesa [promise2]
// cuando esta se ha resuelto (resolve) o rechazado (reject)
promise2.then(result => {
  // en caso de que la promesa se haya resuelto con éxito [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 asíncronas 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 el 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 el 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 conjunto, ya que se han iniciado dos acciones asíncronas. Se vuelve al bucle de eventos, donde, en algún momento, se producirá uno de los eventos [resolved, rejected] de las promesas [promise1, promise2]. Entonces se procesará;
  • luego se volverá al bucle de eventos. Y ahí se procesará el segundo evento [resolved, rejected] de las promesas [promise1, promise2] cuando se produzca;

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 finalice, 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 finalice, lo cual se señalará mediante un evento;
  • línea 5: fin del código principal y vuelta 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 incorporarse al bucle, será procesado por el método [promise1.then] y, a continuación, por el método [promise.finally] (línea 8);
  • líneas 9-11: se produce el mismo mecanismo cuando [promise2] pasa del estado [pending] al [resolved];

11.5. script [async-05]

Volvamos al código del constructor de un objeto [Promise]:

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

const promise=new Promise(function(resolve, reject){
// se inicia una tarea asíncrona
// …
// si se realiza con éxito: llamar a resolve(result), donde [result] es el resultado de la tarea asíncrona;
// si falla: llamar a reject(error), donde [error] es un objeto que encapsula el error producido;
}
// se suscribe a los eventos emitidos por la tarea asíncrona

En la línea 2, se inicia la tarea asíncrona de [Promise]. A menudo necesita más parámetros que los que le pasa la función que la encapsula, es decir, los parámetros de [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:

'use strict';

// definición de la función asíncrona
function uneFonctionAsynchrone (p1, p2, , pn){
 return new Promise(function(resolve, reject){
     //: se inicia una tarea asíncrona con los parámetros (P1, p2, , pn)
    // 
     // si se ejecuta correctamente: llamar a resolve(result), donde [result] es el resultado de la tarea asíncrona;
     // si falla: llamar a reject(error), donde [error] es un objeto que encapsula el error producido;
}
// sesuscribimos a los eventos [resolved, rejected] que emitirá la función asíncrona [uneFonctionAsynchrone]

// Un rato más tarde, se invoca la función asíncrona [uneFonctionAsynchrone]
uneFonctionAsynchrone(e1, e2, , en) ;

El siguiente script:

  • define dos funciones asíncronas que devuelven un [Promise];
  • inicia su ejecución en paralelo y espera a que ambas hayan finalizado para realizar una determinada tarea;

'use strict';

// Se pueden definir funciones asíncronas que devuelvan un tipo [Promise]
// y, a continuación, 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 asíncronas en paralelo
// y se espera a que ambas hayan finalizado
// el «then» solo se ejecuta si ambas funciones han emitido el evento [resolved]
// el «catch» se ejecuta en cuanto una de las dos funciones emite 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 asíncronas
  .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 asíncronas 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 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;
    • devolverán al código llamante dos promesas [promise1, promise2];
    • la ejecución volverá entonces al código que las ha llamado, que continuará su ejecución;
    • al cabo de aproximadamente 1 segundo, [async01] emitirá un evento para indicar que ha finalizado 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 unos 2 segundos, se producirá el mismo proceso para [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 otro modo, [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]. Dicho de otro modo, [f2] se ejecuta tan pronto como falle una de las promesas de la matriz;
    • la función [f3] del método [finally] se ejecutará tras la ejecución de uno de los métodos [then, catch], por lo que siempre se ejecutará;

La ejecución del código da 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 asíncronas [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 de los datos solicitados tienen lugar al mismo tiempo;
  • línea 5: el código principal del script ha finalizado. Queda por esperar a que finalicen las dos tareas asíncronas [async01, async02];
  • línea 6: la tarea asíncrona [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] al estado [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 una matriz [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 un código síncrono. La gestión de eventos queda completamente oculta, lo que facilita la comprensión del código.

Retomamos el ejemplo anterior y le introducimos las siguientes modificaciones:

  • añadimos una tercera función asíncrona [async03] que devuelve su resultado mediante el método [Promise.reject], el cual, a su vez, indica al bucle de eventos que ha «fallado» al realizar su tarea;
  • se ejecutan secuencialmente las tres funciones asíncronas [async01, async02, async03]. En el ejemplo anterior, se habían ejecutado en paralelo las funciones asíncronas [async01, async02];
  • antes de la aparición de las palabras clave [async/await], la ejecución secuencial de acciones asíncronas se realizaba mediante funciones [Promise] anidadas unas dentro de otras. En cuanto había varias acciones asíncronas que ejecutar de este modo, 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 asíncronas se realiza con una sintaxis similar a la de la ejecución de tareas síncronas:

// 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 asíncronas ha fallado
    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]. Se trata, por tanto, 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 llamante, 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] ha pasado a ser 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 recuperado 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 asíncronas en paralelo con la siguiente sintaxis:


try {
    // ejecución paralela de las tareas asíncronas
    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 asíncronas ha fallado
    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: se trata de una operación bloqueante: se espera a que las tres tareas asíncronas 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 asíncronas 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 la 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 asíncronas indistintamente de forma secuencial o en paralelo, todo ello con una sintaxis similar a la de un código síncrono. Por lo tanto, conviene dar prioridad a esta sintaxis, 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 asíncronas
// 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 asíncrona 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 dar lugar a 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 dar lugar a un resultado complejo
      // aquí se produce 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 con 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 asíncronas ha fallado
    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 asíncronas
  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 asíncronas ha fallado
    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();

// se mostrará antes de los distintos mensajes de las funciones asíncronas 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