Skip to content

11. Programação orientada a eventos e funções assíncronas

Image

Uma função assíncrona é uma função cuja execução é iniciada, mas cujo resultado não é aguardado. Quando a execução é concluída, a função assíncrona emite um evento e transmite o seu resultado através desse evento.

Este modo de funcionamento é adequado para execução num navegador web. De facto, uma aplicação em execução num navegador é uma aplicação orientada por eventos: a aplicação reage a eventos, principalmente desencadeados pelo utilizador (cliques, movimentos do rato, introdução de texto, etc.). As aplicações JavaScript em execução num navegador interagem com serviços externos através do protocolo HTTP. As funções HTTP nativas do JavaScript são assíncronas: são iniciadas e a receção de uma resposta do serviço externo solicitado é sinalizada por um evento que é adicionado ao conjunto de eventos geridos pela aplicação.

Os scripts a seguir serão executados pelo [node.js] e não por um navegador. O [node.js] também possui um modelo de execução orientado a eventos:

  • o [node.js], tal como um navegador, utiliza um ciclo de eventos para executar um script;
  • a execução do código principal do script é o primeiro evento executado;
  • se este código principal tiver iniciado tarefas assíncronas, a execução continua até que essas tarefas assíncronas sejam concluídas. Estas tarefas emitem um evento quando terminam. Estes eventos são colocados em fila no ciclo de eventos;
  • o script principal deve subscrever estes eventos se quiser recuperar os resultados das ações assíncronas;
  • o script não está concluído até que todos os eventos que emitiu tenham sido processados;

11.1. script [async-01]

O script a seguir demonstra o comportamento de um script que contém uma ação assíncrona.


'use strict';
 
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
 
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
 
// setTimeout arms a 1000 ms timer (2nd parameter) and immediately returns the timer number
// when the timer has run out of 1000 ms, it emits an event which is queued by the runtime
// when the event is processed by the runtime, the function (1st parameter) is executed
setTimeout(function () {
  // this code will be executed when the timer reaches value 0
  console.log("[fin de l'action asynchrone setTimeout],", heure(débutScript));
}, 1000)
 
// will be displayed before the internal timer function msg
console.log("[fin du code principal du script],", heure(débutScript));
 
// time and duration display utility
function heure(début) {
  // current time
  const now = moment(Date.now());
  // time formatting
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  // is it necessary to calculate a duration?
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // format time + duration
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // result
  return result;
}
  • linha 4: importamos a biblioteca [moment] para formatar datas (linha 27);
  • linha 5: importamos a biblioteca [sprintf-js] para formatar durações (linha 34);
  • linha 8: registamos a hora de início do script;
  • linha 9: exibimos essa hora utilizando o método [time] nas linhas 20–34;
  • linhas 14–17: a função [setTimeout] tem dois parâmetros (f, duration): f é uma função que é executada quando [duration] milissegundos se tiverem decorrido;
  • linha 14: quando o script é executado, a função [setTimeout] é executada:
    • linha 17: é iniciado um temporizador de 1000 ms e começa a contagem decrescente até chegar a 0. A função [setTimeout] é concluída assim que o temporizador é inicializado e a contagem decrescente começa. Não espera que a contagem termine. Devolve um número de identificação para o temporizador utilizado e a execução passa para a instrução seguinte, a linha 20. Aqui, o resultado de [setTimeout] não é utilizado;
  • linha 16: esta mensagem será exibida no final do atraso de 1000 ms da função [setTimeout];
  • linhas 15–16: a função f, o primeiro parâmetro da função [setTimeout], será executada no final do atraso de 1000 ms. A mensagem na linha 16 será então exibida;
  • linha 20: esta mensagem será exibida antes da mensagem na linha 16;

Função de tempo:

  • linha 23: a função aceita um parâmetro opcional [time], que é a hora de início de uma operação cuja duração deve ser exibida;
  • linhas 25–27: a hora atual é calculada e formatada;
  • linha 29: se o parâmetro [start] estiver presente, então deve ser calculada uma duração;
  • linha 30: a duração da operação. Isto resulta num número de milissegundos;
  • linhas 31–32: este número de milissegundos é dividido em segundos e milissegundos;
  • linha 34: a duração é adicionada à hora;

Execução


[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
  • linha 4, podemos ver que a ação assíncrona [setTimeout] terminou aproximadamente 1 segundo após o fim do código principal do script;
  • Linha 6: O tempo apresentado na linha 3 é o tempo em que o código principal termina. Se o código principal tiver iniciado tarefas assíncronas, o script não está concluído até que todas as tarefas assíncronas tenham sido executadas. A duração apresentada na linha 6 é o tempo total de execução do script (código principal + tarefas assíncronas);

A função [setTimeout] permite-nos simular tarefas assíncronas num ambiente [node.js]. Na verdade, a função [setTimeout] comporta-se como uma tarefa assíncrona:

  • ela devolve um resultado imediatamente — neste caso, um ID de temporizador — utilizando o mecanismo padrão da função (return);
  • pode, posteriormente (o que ainda não é o caso acima), devolver outros resultados através de eventos que são então processados pelo ciclo de eventos [node.js];
  • na maioria dos casos que se seguem, haverá dois eventos desse tipo:
    • um evento que poderia ser chamado de [success], que será emitido pela tarefa assíncrona que concluiu com sucesso a sua tarefa. Um dado — o resultado da tarefa — está associado ao evento emitido;
    • um evento que poderia ser chamado de [failure], que é emitido pela tarefa assíncrona que não conseguiu concluir a sua tarefa. Um dado — tipicamente um objeto que descreve o erro — está associado ao evento emitido. Erros possíveis com uma tarefa assíncrona na Internet, por exemplo, seriam «rede indisponível», «máquina do servidor não existe», «tempo limite excedido», ...
  • o código principal que iniciou uma tarefa assíncrona pode subscrever os eventos que esta tarefa provavelmente emitirá. Quando um destes é emitido, o código principal é notificado e pode acionar a execução de uma função específica concebida para lidar com o evento. Esta função recebe como parâmetro os dados que a tarefa assíncrona associou ao evento emitido;

11.2. script [async-02]

Neste script, a função assíncrona [setTimeout] emitirá eventos para comunicar dados ao código que se inscreveu para os receber.

O acesso aos eventos [node.js] requer bibliotecas adicionais. Escolhemos a biblioteca [events], que instalamos utilizando o [npm]:

Image

O script [async-02] é o seguinte:


'use strict';
 
// asynchronous functions can return a result by emitting an event
// the main code can retrieve these results by subscribing to the events issued
 
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';
 
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// an event transmitter
const eventEmitter = new EventEmitter();
 
// setTimeout sets a 1000 ms timer (2nd parameter) and immediately returns the timer number
// when the timer has run out of 1000 ms, it emits an event which is queued by the runtime
// when the event is processed by the runtime, the function (1st parameter) is executed
setTimeout(function () {
  // this code will be executed when the timer reaches value 0
  console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
  // an event is issued to indicate that a result is available
  eventEmitter.emit("timer1Success", { success: 4 });
  // another event is issued to indicate that another result is available
  eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)
 
// subscribe to evt [timer1Success]
eventEmitter.on('timer1Success', (result) => {
  console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Success]", result, heure(débutScript)));
});
 
// subscribe to evt [timer1Failure]
eventEmitter.on('timer1Failure', (result) => {
  console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Failure]", result, heure(débutScript)));
});
 
// will be displayed before the msg of evts issued by the function associated with [timer1]
console.log("[fin du code principal du script],", heure(débutScript));
 
// time and duration display utility
function heure(début) {
  // current time
  const now = moment(Date.now());
  // time formatting
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  // is it necessary to calculate a duration?
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // format time + duration
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // result
  return result;
}

Comentários

  • linha 9: importamos a classe [EventEmitter] da biblioteca [events]. Isto é novo: até agora, só tínhamos importado objetos e funções literais;
  • linha 15: criamos um emissor de eventos [node.js] instanciando a classe [EventEmitter] com a palavra-chave [new];
  • Linhas 20–27: a função assíncrona [setTimeout]. Ela emitirá dois eventos quando executada:
    • linha 24, o evento [timer1Success] com o objeto {success: 4} como seu valor associado;
    • linha 26, o evento [timer1Failure] com o objeto {failure: 6} como seu valor associado;
    • uma função assíncrona pode emitir quantos eventos quiser. Mencionámos anteriormente que, na maioria das vezes, emite um dos dois eventos [success, failure], e não ambos, como estamos a fazer aqui;
  • linha 20: a execução de [setTimeout] é instantânea: um temporizador é definido e o seu ID é devolvido ao código de chamada. Os eventos serão emitidos mais tarde, neste caso 1 segundo depois;
  • desencadear eventos é inútil se não houver código para os tratar quando ocorrem. É por isso que o código principal deve subscrever ambos os eventos [timer1Success, timer1Failure] se quiser tratá-los, especificamente para recuperar os dados associados a estes eventos;
  • Linhas 30–32: O código principal subscreve o evento [timer1Success]. Quando o ouvinte de eventos [node.js] processar este evento, chamará a função que constitui o segundo parâmetro do método [eventEmitter.on], passando-lhe os dados (aqui denominados [result]) associados ao evento [timer1Success];
  • linha 31: a função de tratamento de eventos exibirá o JSON dos dados associados ao evento, bem como a hora atual;
  • linhas 35–37: utilizando código semelhante, o código principal subscreve o evento [timer1Failure];
  • A subscrição de um evento (primeiro parâmetro) não executa imediatamente o código da função [callback] (segundo parâmetro). Esta função só será executada após a ocorrência do evento;
  • linha 40: o código do script principal terminou, mas o próprio script ainda não, uma vez que o código principal iniciou uma tarefa assíncrona. O script completo não terminará até que esta tarefa assíncrona esteja concluída;

Eis o que os resultados mostram:


[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
 
  • linha 3: fim do código principal 10 ms após o início do script;
  • linha 4: início da função encapsulada no temporizador de 1000 ms, aproximadamente 1 segundo após o início do script;
  • linha 5: processamento do evento [‘timer1Success’], 2 ms depois;
  • linha 6: processamento do evento [‘timer1Failure’], 1 ms após o evento [‘timer1Success’];
  • linha 8: fim do script global com uma duração total de 1,627 segundos;

11.3. script [async-03]

O script a seguir demonstra outro aspeto do ciclo de eventos [node.js]:

  • o ciclo executa os eventos um após o outro, geralmente na ordem em que chegam. Alguns sistemas operativos atribuem prioridades aos eventos, que são então processados por ordem de prioridade em vez de por ordem de chegada;
  • o ciclo executa apenas um evento de cada vez. O evento seguinte só é processado depois de o anterior ter terminado. Num sistema orientado por eventos, deve, portanto, evitar escrever código que monopolize o processador durante longos períodos, uma vez que os eventos não serão processados quando ocorrem, mas sim mais tarde, quando o ciclo de eventos chegar a eles. Isto resulta numa aplicação que não é muito «responsiva»;

O script [async-03] demonstra um exemplo deste fenómeno:


'use strict';
 
// asynchronous functions can return a result by emitting an event
// the main code can retrieve these results by subscribing to the events issued
 
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';
 
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// an event transmitter
const eventEmitter = new EventEmitter();
 
// setTimeout sets a 1000 ms timer (2nd parameter) and immediately returns the timer number
// when the timer has run out of 1000 ms, it emits an event which is queued by the runtime
// when the event is processed by the runtime, the function (1st parameter) is executed
setTimeout(function () {
  // this code will be executed when the timer reaches value 0
  console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
  // an event is issued to indicate that a result is available
  eventEmitter.emit("timer1Success", { success: 4 });
  // another event is issued to indicate that another result is available
  eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)
 
// subscribe to evt [timer1Success]
eventEmitter.on('timer1Success', (result) => {
  console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Success]", result, heure(débutScript)));
});
 
// subscribe to evt [timer1Failure]
eventEmitter.on('timer1Failure', (result) => {
  console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Failure]", result, heure(débutScript)));
});

// slightly intensive synchronous code that prevented the main code from completing before the end of [timer1]
for (let i = 0; i < 1000000; i++) {
  for (let j = 0; j < 10000; j++) {
    i + i ^ 2 + i ^ 3;
  }
}
 
// will be displayed before the msg of evts issued by the function associated with [timer1]
console.log("[fin du script],", heure(débutScript));
 
// time and duration display utility
function heure(début) {
 ...
}

Comentários

  • Este código é do exemplo anterior [async-02], ao qual foram adicionadas as linhas 39–44;
  • Linhas 20–27: A função [setTimeout] foi programada para executar uma função assíncrona interna após um atraso de um segundo. Após este segundo ter decorrido, a execução da função assíncrona do temporizador não ocorre imediatamente: é colocado um evento no ciclo de execução para a solicitar. Se o ciclo de execução estiver ocupado a processar outro evento, a execução da função assíncrona do temporizador terá de esperar;
  • linhas 20–27: assim que a função [setTimeout] define o seu temporizador para um atraso de um segundo, liberta o processador e devolve o controlo ao código de chamada. O código de chamada continua com as linhas 30–37, que são subscrições de eventos e têm um tempo de execução insignificante;
  • o código principal continua com as linhas 40–44, que formam um ciclo de 1.010 iterações. Este código estará em execução quando o temporizador acionar o seu evento «fim do atraso de 1 segundo». Este evento é então colocado no ciclo de eventos, mas deve esperar até que o código principal do script tenha terminado a execução para ter a oportunidade de ser processado;
  • Linha 47: fim do código principal do script. É após esta exibição final que o evento de fim do temporizador pode ser processado e a função assíncrona interna de [setTimeout] pode ser executada;

O script produz os seguintes 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

Comentários

  • linha 3: vemos que o código principal do script demorou 9 segundos a ser executado. Quaisquer eventos que ocorreram durante este tempo foram colocados na fila do ciclo de eventos;
  • linha 4: vemos que o evento [timer end] foi processado 5 ms após o código principal ter terminado. Foi emitido aproximadamente 1 s após o início do script, mas teve de esperar mais 8 s para ser finalmente processado;

A principal lição a retirar deste exemplo é que, num sistema orientado por eventos, o código nunca deve ocupar o processador por muito tempo. Se tiver código síncrono que demora muito tempo a executar, deve encontrar uma forma de o dividir em tarefas assíncronas mais curtas que sinalizem a sua conclusão com um evento.

11.4. script [async-04]

O script [async-04] demonstra outro mecanismo, chamado [Promise], uma promessa de um resultado. Este mecanismo evita a necessidade de lidar explicitamente com eventos [node.js]. É tratado implicitamente e, por isso, o programador pode ignorar a existência destes eventos. Compreendê-los, no entanto, ajudará o programador a entender melhor como funciona o [Promise], que à primeira vista parece complexo.

O tipo [Promise] é uma classe JavaScript. O seu construtor aceita uma função assíncrona como parâmetro, à qual passa dois parâmetros tradicionalmente chamados [resolve] e [reject]. Estes poderiam ter nomes diferentes;

1
2
3
4
5
6
7
const promise=new Promise(function(resolve, reject){
     // an asynchronous task is launched
    
     // if successful: call resolve(result) where [result] is the result of the asynchronous task;
     // if unsuccessful: call reject(error) where [error] is an object encapsulating the error encountered;
}
// subscribe to events issued by the [Promise] asynchronous task
  • o construtor [Promise] faz duas coisas:
    • cria um evento para acionar a execução da função [function(resolve, reject)] que lhe é passada como parâmetro, mas não aguarda o seu resultado e devolve imediatamente um objeto [Promise] ao código de chamada. Este objeto pode ter quatro estados:
      • [pending]: a ação assíncrona que devolveu o [Promise] ainda não está concluída;
      • [fulfilled]: a ação assíncrona que devolveu o [Promise] foi concluída com sucesso;
      • [rejected]: a ação assíncrona que devolveu o [Promise] falhou;
      • [settled]: a ação assíncrona que devolveu o [Promise] foi concluída;

Quando o construtor devolve o seu resultado, o objeto [Promise] criado encontra-se no estado [pending], aguardando os resultados da função assíncrona;

  • (continuação)
    • A tarefa assíncrona nas linhas 2 a 5 é iniciada imediatamente. As tarefas assíncronas são, na maioria das vezes, tarefas de entrada/saída que se dividem da seguinte forma:
      1. execução de código síncrono para iniciar a operação de E/S com outro componente, como um servidor remoto;
      2. aguardar uma resposta desse componente;
      3. processamento dessa resposta;

A Fase 2 — a espera pelo componente externo — é a que consome mais recursos. Em vez de esperar:

  • o recebimento dos dados solicitados ao componente externo será sinalizado por um evento;
  • no código síncrono que se segue à fase 1 (linha 7 do código de exemplo), iremos subscrever este evento e, em seguida, a dada altura, regressar ao ciclo de eventos [node.js]. O evento seguinte na lista de eventos pendentes será então processado;
  • durante a fase 2, há execução paralela, mas em dispositivos diferentes:
    • o processador para o ciclo de eventos;
    • um componente externo (disco, base de dados, servidor remoto) para recuperar os dados solicitados;
  • No final da fase 2, assim que a operação de E/S tiver obtido os dados solicitados, será acionado um evento para indicar que o resultado da E/S está disponível. Este evento irá então juntar-se aos outros na fila de eventos;
  • quando chegar a sua vez, será processado. A função associada a este evento (linha 7 do código de exemplo) será então executada;

Este modo de funcionamento ajuda a evitar tempo de inatividade: a situação em que o processador aguarda uma resposta de um dispositivo mais lento do que ele próprio;

  • (continuação)
    • assim que a tarefa assíncrona nas linhas 2 e 5 tiver sido iniciada e tiver concluído o seu trabalho, pode devolver um resultado ao código de chamada utilizando as duas funções [resolve, reject] que o construtor [Promise] lhe passou como parâmetros. A convenção é a seguinte:
      • a tarefa assíncrona sinaliza sucesso através de [resolve(result)]. Isto equivale a adicionar um evento ao ciclo de eventos [node.js] que poderia ser chamado [resolved], com [result] como os dados associados;
      • a tarefa assíncrona sinaliza uma falha através de [reject(error)]. Isto equivale a adicionar um evento ao ciclo de eventos [node.js] que poderia ser chamado [rejected], com [error] como os dados associados — tipicamente um objeto que detalha o erro que ocorreu;
      • o código de chamada deve, portanto, subscrever estes dois eventos para ser notificado quando o resultado da função assíncrona estiver disponível;

Após a tarefa assíncrona encapsulada no [Promise] ter terminado a sua execução, o estado do objeto [promise] devolvido pelo construtor [Promise(…)] altera-se:

  • o evento [resolved] altera o seu estado de [pending] para [resolved];
  • o evento [rejected] muda do estado [pending] para [rejected];

A subscrição dos eventos [resolved] e [rejected] da tarefa assíncrona é feita utilizando métodos da classe [Promise] com a seguinte sintaxe:

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

onde:

  • f1 é uma função executada quando o estado [promise] muda de [pending] para [resolved], ou seja, quando a tarefa assíncrona concluiu com sucesso o seu trabalho. Recebe o valor [result] como parâmetro, passado pela instrução [resolve(result)] da tarefa assíncrona;
  • f2 é uma função executada quando o estado de [promise] muda de [pending] para [rejected], ou seja, quando a tarefa assíncrona não consegue concluir o seu trabalho. Recebe o valor [error] como parâmetro, passado pela instrução [reject(error)] da tarefa assíncrona;
  • f3 é uma função executada após a execução dos métodos [then] ou [catch], pelo que é sempre executada. Não recebe parâmetros;

Esta sintaxe oculta completamente os eventos aos quais nos subscrevemos. No entanto, trata-se de uma subscrição e, tal como no exemplo anterior, não executa imediatamente as funções [f1, f2, f3]. Estas serão executadas — ou não — quando ocorrer um dos eventos [resolved, rejected] aos quais nos subscrevemos.

O script [async-04] demonstra este mecanismo:


'use strict';
 
// it is possible to obtain the results (success, failure) of an asynchronous function
// without explicitly using events thanks to the [Promise] class
// this class implicitly uses events, but these are not visible in the code
 
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
 
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure(débutScript));
 
// definition of an asynchronous task using a promise [Promise]
// the asynchronous task is the [Promise] constructor parameter
const débutPromise1 = moment(Date.now());
const promise1 = new Promise(function (resolve) {
  // log
  console.log("[début fonction asynchrone de promise1],", heure(débutPromise1));
  // asynchronous code
  setTimeout(function () {
    console.log("[fin fonction asynchrone de promise1],", heure(débutPromise1));
    // the asynchronous task returns a result with the [resolve] function
    // the promise is fulfilled
    resolve('[réussite]');
  }, 1000)
});
 
// we can know the result of the promise [promise1]
// when it has been resolved or rejected
// the following instruction is a subscription to the [resolved] event via the [then] method
// and to the [rejected] event via the [catch] method
// the [finally] method is executed whether after a then or a catch
promise1.then(result => {
  // promise success stories [evt resolved]
  console.log(sprintf("[promise1.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
  // error case [evt rejected]
  console.log(sprintf("[promise1.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
  // executed in all cases
  console.log("[promise1.finally]", heure(débutPromise1));
});
 
// definition of an asynchronous task using a promise [Promise]
const débutPromise2 = moment(Date.now());
const promise2 = new Promise(function (resolve, reject) {
  // log
  console.log("[début fonction asynchrone de promise2],", heure(débutPromise1));
  // asynchronous task
  setTimeout(function () {
    console.log("[fin fonction asynchrone de promise2],", heure(débutPromise2));
    // the asynchronous task returns a result with the [reject] function
    // the promise is lost
    reject('[échec]');
  }, 2000)
});
 
// we can know the result of the promise [promise2]
// when it has been resolved or rejected
promise2.then(result => {
  // promise success stories [evt resolved]
  console.log(sprintf("[promise2.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
  // error case [evt rejected]
  console.log(sprintf("[promise2.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
  // executed in all cases
  console.log(sprintf("[promise2.finally], %s", heure(débutPromise2)));
});
 
// will be displayed before the msg of asynchronous functions and those of associated events
console.log("[fin du code principal du script],", heure(débutScript));
 
// utility
function heure(début) {
  // current time
  const now = moment(Date.now());
  // time formatting
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // formatting duration
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // result
  return result;
}

Comentários

  • linhas 18–28: criação de uma [Promise promise1]. A sua função assíncrona devolve o resultado através de um evento após um segundo. Assim que esta operação assíncrona é iniciada (o temporizador é ativado), não esperamos que ela devolva o resultado e avançamos imediatamente para o código na linha 35;
  • linhas 35–44: subscrevemos os dois eventos [resolved, rejected] que a função assíncrona interna de [promise1] pode emitir;
  • linhas 46–71: repetimos a mesma sequência de código de antes para uma segunda promessa [promise2];
  • Linha 74: O corpo principal do script terminou a execução, mas o script como um todo ainda não, porque foram iniciadas duas ações assíncronas. Regressamos ao ciclo de eventos, onde, em algum momento, um dos eventos [resolved, rejected] para as promessas [promise1, promise2] ocorrerá. Será então tratado;
  • depois voltamos ao ciclo de eventos. E aí, o segundo evento [resolved, rejected] das promessas [promise1, promise2] será tratado quando ocorrer;

Execução


[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

Comentários

  • linha 3: a função assíncrona de [promise1] é iniciada, mas não esperamos pela sua conclusão, que será sinalizada por um evento;
  • linha 4: a função assíncrona de [promise2] é iniciada, mas não esperamos que ela termine, o que será sinalizado por um evento;
  • linha 5: fim do código principal e retorno ao ciclo de eventos;
  • linha 6: processamento do evento [fim da função assíncrona de promise1]. O estado de [promise1] mudará para [resolved]. Um evento sinaliza isso;
  • linha 7: uma vez que [promise2] ainda não terminou o seu trabalho, o evento [promise1 resolved] que acabou de ser adicionado ao ciclo será tratado pelo método [promise1.then] e, em seguida, pelo método [promise.finally] (linha 8);
  • linhas 9–11: o mesmo mecanismo ocorre quando [promise2] muda do estado [pending] para [resolved];

11.5. script [async-05]

Voltemos ao código do construtor do objeto [Promise]:

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

const promise=new Promise(function(resolve, reject){
// an asynchronous task is launched
// 
// if successful: call resolve(result) where [result] is the result of the asynchronous task;
// if unsuccessful: call reject(error) where [error] is an object encapsulating the error encountered;
}
// subscribe to events issued by the asynchronous task

Linha 2: a tarefa assíncrona do [Promise] é iniciada. Muitas vezes, requer mais parâmetros do que apenas os parâmetros [resolve, reject] que lhe são passados pela função que o encapsula. Neste caso, encapsulamos a criação do [Promise] numa função que lhe passará os parâmetros de que a sua função assíncrona necessita:

'use strict';

// definition of the asynchronous function
function uneFonctionAsynchrone (p1, p2, , pn){
 return new Promise(function(resolve, reject){
     // an asynchronous task is launched with parameters (P1, p2, ..., pn)
    // 
     // if successful: call resolve(result) where [result] is the result of the asynchronous task;
     // if unsuccessful: call reject(error) where [error] is an object encapsulating the error encountered;
}
// subscribe to [resolved, rejected] events to be sent by the asynchronous function [uneFonctionAsynchrone]

// some time later, the asynchronous function [uneFonctionAsynchrone] is called
uneFonctionAsynchrone(e1, e2, , en) ;

O seguinte script:

  • define duas funções assíncronas que devolvem uma [Promise];
  • inicia a sua execução em paralelo e aguarda que ambas terminem antes de executar uma determinada tarefa;

'use strict';
 
// we can define asynchronous functions that render a [Promise] type
// they can then be tagged with the keyword [async]
 
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
 
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
 
// an asynchronous function that renders a promise [Promise]
function async01(p1) {
  return new Promise((resolve) => {
    console.log("[début de la tâche asynchrone async01]");
    // the asynchronous task
    const débutAsync01 = moment(Date.now());
    setTimeout(function () {
      console.log("[fin de la tâche asynchrone async01],", heure(débutAsync01));
      // the asynchronous task can render a complex result
      resolve({
        prop1: [10, 20, 30],
        prop2: "abcd",
        prop3: p1,
      });
    }, 1000)
  });
}
 
// an asynchronous function that renders a promise [Promise]
function async02(p1, p2) {
  return new Promise(resolve => {
    console.log("[début de la tâche asynchrone async02]");
    // asynchronous task
    const débutAsync02 = moment(Date.now());
    setTimeout(function () {
      console.log("[fin de la tâche asynchrone async02],", heure(débutAsync02));
      // the asynchronous task can render a complex result
      resolve({
        prop1: [11, 21, 31],
        prop2: "xyzt",
        prop3: p1 + p2
      });
    }, 2000)
  })
}
 
// run the two asynchronous functions in parallel
// and wait for them both to finish
// the then executes only if both functions have issued the [resolved] event
// the catch is executed as soon as one of the two functions issues the [rejected] event
Promise.all([async01(10), async02(10, 20)])
  // the result is an array [result1, result2] where [result1] is the result emitted by a [resolve] of [async01]
  // and [result2] the result emitted by a [resolve] of [async02]
  .then(result => {
    console.log(sprintf("[promise-all success], %s, result=%j", heure(débutScript), result));
  })
  // error is the result emitted by the first [reject] of one of the two asynchronous functions
  .catch(error => {
    console.log(sprintf("[promise-all error], %s, erreur=%j", heure(débutScript), error));
  })
  // finally is executed after then or catch
  .finally(() => {
    console.log(sprintf("[promise-all finally], %s", heure(débutScript)));
  });
 
// will be displayed before msgs for asynchronous functions and associated events
console.log("[fin du code principal du script],", heure(débutScript));
 
// utility
function heure(début) {
  // current time
  const now = moment(Date.now());
  // time formatting
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // formatting duration
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // result
  return result;
}

Comentários

  • Linhas 15–30: Definimos uma função [async01] que devolve o seu resultado após 1 segundo através de um evento de temporizador. A função [async01] é utilizada no resultado da linha 26;
  • linhas 33–47: Fazemos o mesmo com uma função [async02] que devolve o seu resultado após 2 segundos através de um evento de temporizador. A função [async02] recebe dois parâmetros, que são utilizados no seu resultado na linha 44;
  • Quando as duas funções [async01] e [async02] são chamadas:
    • será iniciado;
    • serão devolvidas duas promessas [promise1, promise2] ao código de chamada;
    • a execução retornará então ao código de chamada, que continuará a ser executado;
    • após aproximadamente 1 segundo, [async01] emitirá um evento para indicar que concluiu o seu trabalho. O evento em questão será colocado na fila do ciclo de eventos associado ao resultado passado por [async01] juntamente com o evento;
    • após aproximadamente 2 segundos, o mesmo processo ocorrerá para [async02];
  • Linha 54: Só agora é que as funções assíncronas [async01, async02] são executadas (notações async01(10) e async02(10,20)). São executadas dentro de uma matriz passada como parâmetro ao método [Promise.all]. Sabemos que [async01, async02] devolvem ambas uma promessa ao código de chamada. Portanto, o parâmetro de [Promise.all] é uma matriz de duas promessas;
  • [Promise.all([promise1, promise2, …, promisen]).then(f1).catch(f2).finally(f3)] é uma subscrição de eventos:
    • [Promise.all] é do tipo [Promise];
    • a função [f1] do método [then] será executada quando todas as promessas [promise1, promise2, …, promisen] na matriz de parâmetros do método [all] tiverem transitado do estado [pending] para o estado [resolved]. Por outras palavras, [f1] será executada quando todas as promessas na matriz tiverem sido concluídas com sucesso;
    • a função [f2] do método [catch] será executada assim que qualquer uma das promessas na matriz mudar do estado [pending] para o estado [rejected]. Por outras palavras, [f2] é executada assim que qualquer uma das promessas na matriz falhar;
    • a função [f3] do método [finally] será executada após a execução de um dos métodos [then, catch], pelo que é sempre executada;

A execução do código produz os seguintes 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
  • linhas 6-7: as duas tarefas assíncronas [async01, async02] são iniciadas. Elas são executadas em paralelo. Não é a execução do seu código que ocorre em paralelo, mas as respetivas esperas pelos dados solicitados acontecem ao mesmo tempo;
  • Linha 5: O corpo principal do script foi concluído. Agora só temos de esperar que as duas tarefas assíncronas [async01, async02] sejam concluídas;
  • linha 6: a tarefa assíncrona [async01] é concluída aproximadamente 1 segundo após ter sido iniciada. Ela retorna um resultado usando a função [resolve], de modo que a sua promessa na matriz na linha 56 do código muda do estado [pending] para [resolved]. Isto não é suficiente para acionar o método [then], linhas 59–60 do código;
  • linha 7: a tarefa assíncrona [async02] é concluída aproximadamente 2 segundos após ser iniciada. Ela retorna um resultado usando a função [resolve], de modo que a sua promessa na matriz na linha 56 do código muda do estado [pending] para [resolved]. O método [then] será executado assim que o ciclo de eventos permitir;
  • linha 8: o método [then] de [Promise.all] é executado. Recebe como parâmetro uma matriz [result1, result2], em que [result1] é o resultado devolvido por [async01] e [result2] é o resultado devolvido por [async02];
  • linha 9: o método [finally] de [Promise.all] é executado;

11.6. script [async-06]

Este novo script demonstra como o uso combinado das palavras-chave [async / await] permite código assíncrono que se assemelha a código síncrono. O tratamento de eventos fica completamente oculto, tornando o código mais fácil de compreender.

Revisamos o exemplo anterior com as seguintes modificações:

  • adicionamos uma terceira função assíncrona [async03], que retorna o seu resultado utilizando o método [Promise.reject], sinalizando assim ao ciclo de eventos que «falhou» na conclusão da sua tarefa;
  • executamos as três funções assíncronas [async01, async02, async03] sequencialmente. No exemplo anterior, tínhamos executado as funções assíncronas [async01, async02] em paralelo;
  • Antes da introdução das palavras-chave [async/await], a execução sequencial de ações assíncronas era conseguida utilizando objetos [Promise] aninhados. Sempre que havia múltiplas ações assíncronas para executar desta forma, o número de promessas aumentava em conformidade, e o código tornava-se menos legível;
  • Com as palavras-chave [async/await], a execução sequencial de tarefas assíncronas utiliza uma sintaxe semelhante à da execução de tarefas síncronas:

// asynchronous function - using async / await
async function main() {
  // sequential execution of asynchronous tasks
  try {
    // execution while waiting for [async01]
    const result1 = await async01(...);
    console.log("[async01 result]=", result1);
    // execution while waiting for [async02]
    const result2 = await async02(...);
    console.log("[async02 result]=", result2);
    // execution while waiting for [async03]
    const result3 = await async03(...);
    console.log("[async03 result]=", result3);
  } catch (error) {
    // one of the asynchronous actions has failed
    console.log(sprintf("[sequential error]= %j, %s", error));
  } finally {
    // completed
    console.log("[fin exécution séquentielle des tâches asynchrones],");
  }
  • linha 6: a função assíncrona [async01] é iniciada (usando a palavra-chave await) e aguardamos que ela retorne o seu resultado através de um dos métodos [Promise.resolve, Promise.reject]. Trata-se, portanto, de uma operação de bloqueio;
  • linha 6: a palavra-chave [await] transforma a operação assíncrona [async01] numa operação de bloqueio. Sabemos que a operação [async01] devolve um resultado de duas formas:
    • ela devolve um objeto [Promise] ao código de chamada quase imediatamente;
    • posteriormente, publica um resultado no ciclo de eventos através dos métodos [Promise.resolve, Promise.reject]. É este último resultado que [result1] recupera na linha 6. O tratamento orientado por eventos da ação [async01] tornou-se invisível;
    • se o resultado [result] de [async01] for publicado através de [Promise.resolve(result)], é atribuído a [result1] na linha 6 e a execução continua para a linha 7;
    • Se o resultado de [async01] for resolvido através de [Promise.reject], isso desencadeia uma exceção e a execução do código prossegue para a linha 14, o bloco catch. O parâmetro da cláusula [catch] é o objeto de erro (error) resolvido por [async01] utilizando a expressão [Promise.reject(error)]. A tarefa assíncrona também pode emitir o erro através de um [throw(error)]. O objeto [error] é aquele capturado em [catch(error)];
    • a palavra-chave [await] deve estar dentro de uma função precedida pela palavra-chave [async], linha 2. Esta palavra-chave indica que a função [main] é uma função assíncrona;
    • na expressão [await f(…)], [f] deve ser uma função assíncrona que devolve um objeto [Promise] ao código de chamada;
  • Fazemos o mesmo para as ações assíncronas [async02] na linha 9 e [async03] na linha 12;

Ainda utilizando as palavras-chave [async / await], é possível executar tarefas assíncronas em paralelo utilizando a seguinte sintaxe:


try {
    // parallel execution of asynchronous tasks
    const result = await Promise.all([async01(...), async02(...), async03(...)]);
    console.log(sprintf("[parallel success], %s, result=%j", heure(débutParallel), result));
  } catch (error) {
    // one of the asynchronous actions has failed
    console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
  } finally {
    // completed
    console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
}
  • linha 3: temos uma operação de bloqueio: aguardamos que as três tarefas assíncronas na matriz [async01(..), async02(..), async03(..)] publiquem os seus resultados no ciclo de eventos utilizando um dos métodos [Promise.resolve, Promise.reject];
  • se as três tarefas assíncronas publicarem os seus resultados utilizando [Promise.resolve], a constante [result] é então a matriz [result1, result2, result3] onde:
    • [result1] é o resultado publicado por [async01] utilizando a expressão [Promise.resolve(result1)];
    • [result2] é o resultado publicado por [async02] utilizando a expressão [Promise.resolve(result2)];
    • [result3] é o resultado publicado por [async03] utilizando a expressão [Promise.resolve(result3)];
  • se qualquer uma das três tarefas publicar o seu resultado utilizando a expressão [Promise.reject(error)], então ocorre uma exceção;
    • a constante [result] na linha 3 não recebe um valor;
    • a execução prossegue diretamente para o bloco [catch] na linha 5;
    • o parâmetro (error) do catch é o objeto (error) publicado pela expressão [Promise.reject(error)];

Ao combinar estas duas sintaxes, podemos executar tarefas assíncronas sequencialmente ou em paralelo, tudo utilizando uma sintaxe semelhante à do código síncrono. Devemos, portanto, preferir esta sintaxe, que é muito mais legível do que as anteriores. Esta sintaxe [async/await] só está disponível desde a versão 6 do ECMAScript. Ainda existe muito código JavaScript que utiliza promessas [Promise]. É por isso que também é importante compreender como funcionam.

O código completo para o script [async-06] é o seguinte:


'use strict';
 
// parallel or sequential execution of several asynchronous tasks
// with the keywords async / await
 
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
 
// start
const débutScript = moment(Date.now());
console.log("[début du code principal du script],", heure());

// an asynchronous function rendering a [Promise]
function async01(débutAsync01) {
  return new Promise(function (resolve) {
    console.log("[début fonction asynchrone async01],", heure());
    // asynchronous function
    setTimeout(function () {
      console.log("[fin fonction asynchrone async01],", heure(débutAsync01));
      // asynchronous action can make a result complex
      // here success
      resolve({
        prop1: [11, 21, 31],
        prop2: "abcd"
      });
    }, 1000)
  });
}
 
// an asynchronous function rendering a [Promise]
function async02(débutAsync02) {
  console.log("[début fonction asynchrone async02],", heure());
  return new Promise(function (resolve) {
    // asynchronous function
    setTimeout(function () {
      console.log("[fin fonction asynchrone async02],", heure(débutAsync02));
      // asynchronous action can make a result complex
      // here success
      resolve({
        prop1: [12, 22, 32],
        prop2: "xyzt"
      });
    }, 2000)
  })
}
 
// an asynchronous function rendering a [Promise]
function async03(débutAsync03) {
  console.log("[début fonction asynchrone async03],", heure());
  return new Promise((resolve, reject) => {
    // asynchronous function
    setTimeout(function () {
      console.log("[fin fonction asynchrone async03],", heure(débutAsync03));
      // asynchronous action can make a result complex
      // here failure
      reject({
        prop1: [13, 23, 33],
        prop2: "échec"
      });
    }, 3000)
  })
}
 
// asynchronous function - using async / await
async function main() {
  const débutSequential = moment(Date.now());
  // sequential execution of asynchronous tasks
  console.log("------------ exécution séquentielle des tâches asynchrones lancée ------------------------")
  try {
    // execution while waiting for [async01]
    const débutAsync01 = moment(Date.now());
    const result1 = await async01(débutAsync01);
    console.log("[async01 result]=", result1);
    // execution while waiting for [async02]
    const débutAsync02 = moment(Date.now());
    console.log("début async02-------------", heure());
    const result2 = await async02(débutAsync02);
    console.log("[async02 result]=", result2);
    // execution while waiting for [async03]
    const débutAsync03 = moment(Date.now());
    console.log("début async03-------------", heure());
    const result3 = await async03(débutAsync03);
    console.log("[async03 result]=", result3);
  } catch (error) {
    // one of the asynchronous actions has failed
    console.log(sprintf("[sequential error]= %j, %s", error, heure(débutSequential)));
  } finally {
    // completed
    console.log("[fin exécution séquentielle des tâches asynchrones],", heure(débutSequential));
  }
 
  const débutParallel = moment(Date.now());
  // parallel execution of asynchronous tasks
  console.log("------------ exécution parallèle des tâches asynchrones lancée ------------------------")
  try {
    const result = await Promise.all([async01(débutParallel), async02(débutParallel), async03(débutParallel)]);
    console.log(sprintf("[parallel success], %s, result=%j", heure(débutParallel), result));
  } catch (error) {
    // one of the asynchronous actions has failed
    console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
  } finally {
    // completed
    console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
  }
 
  // completed
  console.log("[fin de la fonction main],", heure(débutSequential));
}
// execution asynchronous function main
main();
 
// will be displayed before the various msgs for asynchronous functions and their events
console.log("[fin du code principal du script],", heure(débutScript));
 
// utility
function heure(début) {
  // current time
  const now = moment(Date.now());
  // time formatting
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // formatting duration
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // result
  return result;
}

Os resultados da execução são os seguintes:


[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