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 termina, a função assíncrona emite um evento e transmite o seu resultado através desse evento.

Este modo de funcionamento é particularmente adequado para a execução num navegador web. Com efeito, uma aplicação executada num navegador é uma aplicação baseada em eventos: a aplicação reage a eventos, principalmente provocados pelo utilizador (cliques, movimentos do rato, introdução de texto, etc.). As aplicações JavaScript executadas num navegador têm de interagir com serviços externos através do protocolo HTTP. As funções nativas HTTP do JavaScript são assíncronas: são iniciadas e a obtenção da resposta do serviço externo solicitado é sinalizada por um evento que se junta ao conjunto de eventos geridos pela aplicação.

Os seguintes scripts serão executados pelo [node.js] e não por um navegador. O [node.js] também possui um modo de execução por evento:

  • 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 esse código principal tiver iniciado tarefas assíncronas, a execução continua enquanto essas tarefas não estiverem concluídas. Estas emitem um evento quando estão concluídas. Esses eventos são colocados numa fila no ciclo de eventos;
  • o script principal deve subscrever esses eventos se quiser recuperar os resultados das ações assíncronas;
  • o script só termina quando todos os eventos por ele emitidos tiverem sido processados;

11.1. script [async-01]

O script seguinte ilustra o comportamento de um script que inclui uma ação assíncrona.


'use strict';

// importações
import moment from 'moment';
import { sprintf } from 'sprintf-js';

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

// setTimeout inicia um temporizador de 1000 ms (2.º parâmetro) e devolve imediatamente o número desse temporizador
// quando o temporizador atinge os 1000 ms, emite um evento que é colocado na fila do runtime
// quando o evento é processado pelo runtime, a função (1.º parâmetro) é executada
setTimeout(function () {
  // este código será executado quando o temporizador atingir o valor 0
  console.log("[fin de l'action asynchrone setTimeout],", heure(débutScript));
}, 1000)

// será exibido antes da mensagem da função interna do temporizador
console.log("[fin du code principal du script],", heure(débutScript));

// utilitário para exibir a hora e a duração
function heure(début) {
  // hora atual
  const now = moment(Date.now());
  // formatação da hora
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  // é necessário calcular uma duração?
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // formatação da hora + duração
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // resultado
  return result;
}
  • linha 4: importa-se a biblioteca [moment] para poder formatar as datas (linha 27);
  • linha 5: importa-se a biblioteca [sprintf-js] para poder formatar as durações (linha 34);
  • linha 8: regista-se a hora de início do script;
  • linha 9: exibe-se essa hora utilizando o método [heure] das linhas 20-34;
  • linhas 14-17: a função [setTimeout] tem dois parâmetros (f, duração): f é uma função que é executada quando se passaram [durée] milissegundos;
  • linha 14: aquando da execução do script, a função [setTimeout] é executada:
    • linha 17: é ativado um temporizador de 1000 ms e a contagem regressiva começa até atingir, posteriormente, o valor 0. A função [setTimeout] termina assim que o temporizador é inicializado e a contagem regressiva começa. Não aguarda o fim dessa contagem. Devolve um número que identifica o temporizador utilizado e a execução passa para a instrução seguinte, linha 20. Aqui, o resultado de [setTimeout] não é utilizado;
  • linha 16: esta mensagem será exibida no final do intervalo de 1000 ms da função [setTimeout];
  • linhas 15-16: a função f, primeiro parâmetro da função [setTimeout], será executada no final do intervalo de 1000 ms. A mensagem da linha 16 será então exibida;
  • linha 20: esta mensagem será exibida antes da mensagem da linha 16;

Função de hora:

  • linha 23: a função aceita um parâmetro opcional [heure], que corresponde à hora de início de uma operação cuja duração deve ser apresentada;
  • linhas 25-27: calcula-se e formata-se a hora atual;
  • linha 29: se o parâmetro [début] estiver presente, é necessário calcular uma duração;
  • linha 30: a duração da operação. Obtém-se um número de milissegundos;
  • linhas 31-32: este número de milissegundos é decomposto 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
  • na linha 4, verifica-se que a ação assíncrona [setTimeOut] terminou cerca de 1 s após o fim do código principal do script;
  • linha 6: a hora apresentada na linha 3 corresponde à hora em que o código principal terminou. Se este tiver iniciado tarefas assíncronas, o script só termina quando todas as tarefas assíncronas tiverem sido executadas. A duração apresentada na linha 6 corresponde à duração total de execução do script (código principal + tarefas assíncronas);

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

  • retorna um resultado imediatamente, neste caso um número de temporizador, através do mecanismo habitual das funções (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 de [node.js];
  • na maioria dos casos que se seguirão, esses eventos serão dois:
    • um evento a que poderíamos chamar [success], que será emitido pela tarefa assíncrona que concluiu com sucesso o que tinha de fazer. Um dado, o resultado da tarefa, está associado ao evento emitido;
    • um evento a que poderíamos chamar [failure], que será emitido pela tarefa assíncrona que não conseguiu realizar o que devia. Um dado — geralmente um objeto que descreve o erro — está associado ao evento emitido. Erros possíveis, por exemplo, numa tarefa assíncrona da Internet, seriam «rede indisponível», «servidor inexistente», «tempo limite excedido», ...
  • O código principal que iniciou uma tarefa assíncrona pode subscrever os eventos que essa tarefa possa vir a emitir. Quando um desses eventos é emitido, o código principal é notificado e pode acionar a execução de uma função específica destinada a tratar 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] irá emitir eventos para comunicar dados aos códigos que se tenham subscrito aos mesmos.

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

Image

O script [async-02] é o seguinte:


'use strict';

// as funções assíncronas podem devolver um resultado através da emissão de um evento
// o código principal pode recuperar esses resultados subscrevendo os eventos emitidos

// importações
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';

// início
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// um emissor de eventos
const eventEmitter = new EventEmitter();

// setTimeout arma um temporizador de 1000 ms (2.º parâmetro) e devolve imediatamente o número desse temporizador
// quando o temporizador atinge os 1000 ms, emite um evento que é colocado na fila do runtime
// quando o evento é processado pelo runtime, a função (1.º parâmetro) é executada
setTimeout(function () {
  // este código será executado quando o temporizador atingir o valor 0
  console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
  // é emitido um evento para indicar que está disponível um resultado
  eventEmitter.emit("timer1Success", { success: 4 });
  // é emitido outro evento para indicar que está disponível outro resultado
  eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)

// subscreve-se o 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)));
});

// subscreve-se o 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)));
});

// será exibido antes das mensagens dos eventos emitidos pela função associada a [timer1]
console.log("[fin du code principal du script],", heure(débutScript));

// utilitário de exibição da hora e da duração
function heure(début) {
  // hora atual
  const now = moment(Date.now());
  // formatação da hora
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  // é necessário calcular uma duração?
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // formatação da hora + duração
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // resultado
  return result;
}

Comentários

  • na linha 9, importamos a classe [EventEmitter] da biblioteca [events]. Trata-se de uma novidade: até agora, só tínhamos importado objetos literais e funções;
  • linha 15: cria-se um emissor de eventos [node.js], instanciando a classe [EventEmitter] com a palavra-chave [new];
  • linhas 20-27: a função assíncrona [setTimeout]. Esta irá emitir dois eventos durante a sua execução:
    • linha 24, o evento [timer1Success] com o objeto {success : 4} como valor associado;
    • linha 26, o evento [timer1Failure] com o objeto {failure: 6} como valor associado;
    • uma função assíncrona pode emitir tantos eventos quantos desejar. Referimos anteriormente que, na maioria das vezes, ela emite um dos dois eventos [success, failure], e não ambos, como acontece aqui;
  • linha 20: a execução de [setTimeout] é instantânea: é ativado um temporizador e o seu número é devolvido ao código chamador. A emissão dos eventos ocorrerá mais tarde, neste caso, 1 segundo depois;
  • a emissão de eventos é inútil se não houver código para os processar quando ocorrerem. É por isso que o código principal deve subscrever os dois eventos [timer1Success, timer1Failure] se quiser geri-los, nomeadamente recuperar os dados associados a esses eventos;
  • linhas 30-32: o código principal subscreve o evento [timer1Success]. Quando o canal de eventos de [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 do evento exibirá o jSON dos dados associados ao evento, bem como a hora atual;
  • linhas 35-37: com um código semelhante, o código principal subscreve o evento [timer1Failure];
  • A subscrição de um evento (1.º parâmetro) não executa imediatamente o código da função [callback] (2.º parâmetro). Esta só será executada depois de o evento ter ocorrido;
  • linha 40: o código principal do script está concluído, mas não o próprio script, uma vez que o código principal iniciou uma tarefa assíncrona. O script global só estará concluído após o término dessa tarefa assíncrona;

É isso que mostram os resultados obtidos:


[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, cerca de 1 segundo após o início do script;
  • linha 5: processamento do evento [‘timer1Success’], 2 ms mais tarde;
  • linha 6: processamento do evento [‘timer1Failure’], 1 ms depois do evento [‘timer1Success’];
  • linha 8: fim do script global com uma duração total de 1,627 segundos;

11.3. script [async-03]

O script seguinte mostra outro aspeto do ciclo de eventos de [node.js]:

  • o ciclo executa os eventos um após o outro, geralmente pela ordem em que chegam. Alguns OS atribuem prioridades aos eventos, que são então processados por ordem de prioridade e não por ordem de chegada;
  • o ciclo executa apenas um evento de cada vez. O seguinte só é processado quando o processamento do anterior estiver concluído. Num sistema de eventos, deve-se, portanto, evitar escrever código que monopolize o processador durante muito tempo, pois, nesse caso, os eventos não são processados quando ocorrem, mas sim mais tarde, quando o ciclo de eventos chega a eles. Ficamos então com uma aplicação pouco «reativa»;

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


'use strict';

// as funções assíncronas podem devolver um resultado através da emissão de um evento
// o código principal pode recuperar esses resultados subscrevendo os eventos emitidos

// importações
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';

// início
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// um emissor de eventos
const eventEmitter = new EventEmitter();

// setTimeout arma um temporizador de 1000 ms (2.º parâmetro) e devolve imediatamente o número desse temporizador
// quando o temporizador atinge os 1000 ms, emite um evento que é colocado na fila do runtime
// quando o evento é processado pelo runtime, a função (1.º parâmetro) é executada
setTimeout(function () {
  // este código será executado quando o temporizador atingir o valor 0
  console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
  // é emitido um evento para indicar que está disponível um resultado
  eventEmitter.emit("timer1Success", { success: 4 });
  // é emitido outro evento para indicar que está disponível outro resultado
  eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)

// subscreve-se o 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)));
});

// subscreve-se o 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)));
});

// um código síncrono um pouco intensivo que impediu que o código principal terminasse antes do fim de [timer1]
for (let i = 0; i < 1000000; i++) {
  for (let j = 0; j < 10000; j++) {
    i + i ^ 2 + i ^ 3;
  }
}

// será exibido antes das mensagens dos eventos emitidos pela função associada a [timer1]
console.log("[fin du script],", heure(débutScript));

// utilitário de exibição da hora e da duração
function heure(début) {
 ...
}

Comentários

  • este código é o 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 intervalo de um segundo. Passado esse segundo, a execução da função assíncrona do temporizador não ocorre imediatamente: é inserido um evento no ciclo de execução para solicitar a mesma. Se o ciclo de execução estiver ocupado a processar outro evento, a execução da função assíncrona do temporizador terá de aguardar;
  • linhas 20-27: assim que a função [setTimeout] armou o seu temporizador com um intervalo de um segundo, liberta o processador e devolve o controlo ao código chamador. Este 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 1010 iterações. Este código estará em execução quando o temporizador emitir o seu evento de «fim do intervalo de 1 segundo». Este evento é então colocado no ciclo de eventos, mas terá de aguardar o fim da execução do código principal do script para ter a oportunidade de ser processado;
  • linha 47: fim do código principal do script. É após esta última exibição que o evento de fim do temporizador poderá ser processado e a função assíncrona interna do [setTimeout] poderá ser executada;

O script apresenta 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: verifica-se que o código principal do script demorou 9 segundos a ser executado. Os eventos que possam ter ocorrido durante esse período foram colocados em espera no ciclo de eventos;
  • linha 4: verifica-se que o evento [fin du timer] foi processado 5 ms após o fim do código principal. Foi emitido cerca de 1 s após o início do script, mas teve de esperar mais 8 s para ser finalmente processado;

O que se retira deste exemplo é que, num sistema baseado em eventos, um código nunca deve ocupar o processador durante muito tempo. Se tivermos um código síncrono demorado a executar, devemos «arranjar uma forma» de o decompor em tarefas assíncronas mais curtas que sinalizarão a sua conclusão através de um evento.

11.4. script [async-04]

O script [async-04] mostra outro mecanismo, denominado [Promise], uma promessa de resultado. Este mecanismo evita a gestão explícita de eventos [node.js]. Isso é feito implicitamente e o programador pode, assim, ignorar a existência desses eventos. No entanto, compreendê-los permitirá ao programador compreender melhor o funcionamento dos [Promise], que à primeira vista parece complexo.

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

1
2
3
4
5
6
7
const promise=new Promise(function(resolve, reject){
     // é iniciada uma tarefa assíncrona
    
     // se for bem-sucedida: chamar resolve(result), em que [result] é o resultado da tarefa assíncrona;
     // em caso de falha: chamar reject(error), em que [error] é um objeto que encapsula o erro ocorrido;
}
// subscrever os eventos emitidos pela tarefa assíncrona de [Promise]
  • o construtor de [Promise] faz duas coisas:
    • cria um evento para iniciar a execução da função [function(resolve, reject)] que lhe foi passada como parâmetro, mas não aguarda o seu resultado e devolve imediatamente um objeto [Promise] ao código chamador. Este objeto pode apresentar quatro estados:
      • [pending]: a ação assíncrona que gerou a [Promise] ainda não terminou;
      • [fulfilled]: a ação assíncrona que gerou a [Promise] foi concluída com sucesso;
      • [rejected]: a ação assíncrona que gerou a [Promise] terminou com falha;
      • [settled]: a ação assíncrona que gerou a [Promise] foi concluída;

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

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

É a fase 2, de espera pelo dispositivo externo ao processador, que é a mais dispendiosa. Em vez de esperar:

  • a receção dos dados solicitados ao dispositivo externo será sinalizada por um evento;
  • no código síncrono que se seguirá à fase 1 (linha 7 do código de exemplo), iremos subscrever esse evento e, posteriormente, regressar ao ciclo de eventos de [node.js]. O evento seguinte na lista de eventos em espera será então processado;
  • durante a fase 2, existe paralelismo de execução, mas em periféricos diferentes:
    • o processador para o ciclo de eventos;
    • um dispositivo externo (disco, base de dados, servidor remoto) para a pesquisa dos dados solicitados;
  • no final da fase 2, quando a operação de E/S tiver obtido os dados solicitados, será emitido um evento para indicar que o resultado da E/S está disponível. Este evento irá então juntar-se aos outros na lista de espera 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 permite evitar tempos mortos: aqueles em que o processador aguarda a resposta de um periférico mais lento do que ele;

  • (continuação)
    • quando a tarefa assíncrona das linhas 2 e 5 for iniciada e concluir o seu trabalho, tem a possibilidade de devolver um resultado ao código chamador, graças às duas funções [resolve, reject] que o construtor [Promise] lhe passou como parâmetros. A convenção é a seguinte:
      • a tarefa assíncrona sinaliza o sucesso através de [resolve(result)]. Isto equivale a inserir no ciclo de eventos de [node.js] um evento a que se poderia chamar [resolved], com [result] como dado associado;
      • a tarefa assíncrona sinaliza uma falha através de [reject(error)]. Isto equivale a inserir no ciclo de eventos de [node.js] um evento que poderíamos designar por [rejected], com [error] como dado associado — geralmente um objeto que detalha o erro que ocorreu;
      • por isso, o código chamador tem de subscrever estes dois eventos para ser notificado da disponibilidade do resultado da função assíncrona;

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

  • o evento [resolved] faz com que ele passe do estado [pending] para [resolved];
  • o evento [rejected] faz com que ele passe do estado [pending] para [rejected];

A subscrição dos eventos [resolved] e [rejected] da tarefa assíncrona é feita através de 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 de [promise] passa de [pending] para [resolved], ou seja, quando a tarefa assíncrona conclui com sucesso o seu trabalho. Recebe como parâmetro o valor [result], transmitido pela instrução [resolve(result)] da tarefa assíncrona;
  • f2 é uma função executada quando o estado de [promise] passa de [pending] para [rejected], ou seja, quando a tarefa assíncrona falha na execução da sua tarefa. Recebe como parâmetro o valor [error], transmitido pela instrução [reject(errror)] 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 qualquer parâmetro;

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

O script [async-04] ilustra este mecanismo:


'use strict';

// é possível obter os resultados (sucesso, falha) de uma função assíncrona
// sem utilizar explicitamente eventos, graças à classe [Promise]
// esta classe utiliza eventos implicitamente, mas estes não são visíveis no código

// importações
import moment from 'moment';
import { sprintf } from 'sprintf-js';

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

// definição de uma tarefa assíncrona utilizando uma promessa [Promise]
// a tarefa assíncrona é o parâmetro do construtor [Promise]
const débutPromise1 = moment(Date.now());
const promise1 = new Promise(function (resolve) {
  // registo
  console.log("[début fonction asynchrone de promise1],", heure(débutPromise1));
  // código assíncrono
  setTimeout(function () {
    console.log("[fin fonction asynchrone de promise1],", heure(débutPromise1));
    // a tarefa assíncrona devolve um resultado com a função [resolve]
    // a promessa é então cumprida
    resolve('[réussite]');
  }, 1000)
});

// é possível conhecer o resultado da promessa [promise1]
// quando esta tiver sido resolvida (resolve) ou rejeitada (reject)
// a instrução seguinte é uma subscrição do evento [resolved] através do método [then]
// e ao evento [rejected] através do método [catch]
// o método [finally] é executado quer seja após um «then» ou um «catch»
promise1.then(result => {
  // caso de sucesso da promessa  [evt resolved]
  console.log(sprintf("[promise1.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
  // caso de erro  [evt rejected]
  console.log(sprintf("[promise1.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
  // executado em todos os casos
  console.log("[promise1.finally]", heure(débutPromise1));
});

// definição de uma tarefa assíncrona utilizando uma promessa [Promise]
const débutPromise2 = moment(Date.now());
const promise2 = new Promise(function (resolve, reject) {
  // registo
  console.log("[début fonction asynchrone de promise2],", heure(débutPromise1));
  // tarefa assíncrona
  setTimeout(function () {
    console.log("[fin fonction asynchrone de promise2],", heure(débutPromise2));
    // a tarefa assíncrona devolve um resultado com a função [reject]
    // a promessa é então falhada
    reject('[échec]');
  }, 2000)
});

// é possível conhecer o resultado da promessa [promise2]
// quando esta tiver sido resolvida (resolve) ou rejeitada (reject)
promise2.then(result => {
  // caso de sucesso da promessa [evt resolved]
  console.log(sprintf("[promise2.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
  // caso de erro [evt rejected]
  console.log(sprintf("[promise2.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
  // executado em todos os casos
  console.log(sprintf("[promise2.finally], %s", heure(débutPromise2)));
});

// será exibido antes das mensagens das funções assíncronas e das dos eventos associados
console.log("[fin du code principal du script],", heure(débutScript));

// utilitário
function heure(début) {
  // hora atual
  const now = moment(Date.now());
  // formatação da 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);
    // formatação da duração
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // resultado
  return result;
}

Comentários

  • linhas 18-28: criação de um [Promise promise1]. A sua função assíncrona devolve o resultado através de um evento ao fim de um segundo. Uma vez iniciada esta operação assíncrona (ativação de um temporizador), não se aguarda que esta devolva o resultado e passa-se imediatamente para o código da linha 35;
  • linhas 35-44: subscrevemos os dois eventos [resolved, rejected] que a função assíncrona interna da [promise1] pode emitir;
  • linhas 46-71: repete-se a mesma sequência de código que anteriormente para uma segunda promessa [promise2];
  • linha 74: o código principal do script está concluído, mas não o script na sua totalidade, uma vez que foram iniciadas duas ações assíncronas. Regressa-se ao ciclo de eventos, onde, a dada altura, um dos eventos [resolved, rejected] das promessas [promise1, promise2] irá ocorrer. Este será então processado;
  • depois, haverá um regresso ao ciclo de eventos. E aí, o segundo evento [resolved, rejected] das promessas [promise1, promise2] será processado 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 se aguarda a sua conclusão, que será sinalizada por um evento;
  • linha 4: a função assíncrona de [promise2] é iniciada, mas não se aguarda a sua conclusão, que será sinalizada por um evento;
  • linha 5: fim do código principal e regresso ao ciclo de eventos;
  • linha 6: processamento do evento [fin fonction asynchrone de promise1]. O estado de [promise1] passará para [resolved]. Um evento sinaliza isso;
  • linha 7: uma vez que o [promise2] ainda não terminou o seu trabalho, o evento [promise1 resolved], que acaba de ser colocado no ciclo, será processado pelo método [promise1.then] e, em seguida, pelo método [promise.finally] (linha 8);
  • linhas 9-11: o mesmo mecanismo ocorre quando o [promise2] passa do estado [pending] para o [resolved];

11.5. script [async-05]

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

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

const promise=new Promise(function(resolve, reject){
// é iniciada uma tarefa assíncrona
// …
// se for bem-sucedida: chamar resolve(result), em que [result] é o resultado da tarefa assíncrona;
// em caso de falha: chamar reject(error), em que [error] é um objeto que encapsula o erro ocorrido;
}
// subscreve-se os eventos emitidos pela tarefa assíncrona

Na linha 2, é iniciada a tarefa assíncrona do [Promise]. Esta tarefa necessita frequentemente de mais parâmetros do que apenas os parâmetros [resolve, reject] que a função que a encapsula lhe passa. Neste caso, encapsula-se a criação da [Promise] numa função que lhe passará os parâmetros de que a sua função assíncrona necessita:

'use strict';

// definição da função assíncrona
function uneFonctionAsynchrone (p1, p2, , pn){
 return new Promise(function(resolve, reject){
     // é iniciada uma tarefa assíncrona com os parâmetros (P1, p2, , pn)
    // 
     // se for bem-sucedida: chamar resolve(result), em que [result] é o resultado da tarefa assíncrona;
     // em caso de falha: chamar reject(error), em que [error] é um objeto que encapsula o erro ocorrido;
}
// subscreve-sesubscrevem os eventos [resolved, rejected] que serão emitidos pela função assíncrona [uneFonctionAsynchrone]

// algum tempo depois, a função assíncrona [uneFonctionAsynchrone] é chamada
uneFonctionAsynchrone(e1, e2, , en) ;

O seguinte script:

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

'use strict';

// é possível definir funções assíncronas que devolvem um tipo [Promise]
// podem então ser marcadas com a palavra-chave [async]

// importações
import moment from 'moment';
import { sprintf } from 'sprintf-js';

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

// uma função assíncrona que devolve uma promessa [Promise]
function async01(p1) {
  return new Promise((resolve) => {
    console.log("[début de la tâche asynchrone async01]");
    // a tarefa assíncrona
    const débutAsync01 = moment(Date.now());
    setTimeout(function () {
      console.log("[fin de la tâche asynchrone async01],", heure(débutAsync01));
      // a tarefa assíncrona pode devolver um resultado complexo
      resolve({
        prop1: [10, 20, 30],
        prop2: "abcd",
        prop3: p1,
      });
    }, 1000)
  });
}

// uma função assíncrona que devolve uma promessa [Promise]
function async02(p1, p2) {
  return new Promise(resolve => {
    console.log("[début de la tâche asynchrone async02]");
    // tarefa assíncrona
    const débutAsync02 = moment(Date.now());
    setTimeout(function () {
      console.log("[fin de la tâche asynchrone async02],", heure(débutAsync02));
      // a tarefa assíncrona pode devolver um resultado complexo
      resolve({
        prop1: [11, 21, 31],
        prop2: "xyzt",
        prop3: p1 + p2
      });
    }, 2000)
  })
}

// as duas funções assíncronas são executadas em paralelo
// e aguarda-se que ambas tenham terminado
// o «then» só é executado se ambas as funções tiverem emitido o evento [resolved]
// o «catch» é executado assim que uma das duas funções emitir o evento [rejected]
Promise.all([async01(10), async02(10, 20)])
  //; o resultado é um array [result1, result2], em que [result1] é o resultado emitido por um [resolve] de [async01]
  // e [result2] o resultado emitido por um [resolve] de [async02]
  .then(result => {
    console.log(sprintf("[promise-all success], %s, result=%j", heure(débutScript), result));
  })
  // erro é o resultado emitido pelo primeiro [reject] de uma das duas funções assíncronas
  .catch(error => {
    console.log(sprintf("[promise-all error], %s, erreur=%j", heure(débutScript), error));
  })
  // o «finally» é executado após o «then» ou o «catch»
  .finally(() => {
    console.log(sprintf("[promise-all finally], %s", heure(débutScript)));
  });

// será exibido antes das mensagens das funções assíncronas e dos eventos associados
console.log("[fin du code principal du script],", heure(débutScript));

// utilitário
function heure(début) {
  // hora atual
  const now = moment(Date.now());
  // formatação da 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);
    // formatação da duração
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // resultado
  return result;
}

Comentários

  • linhas 15-30: define-se 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: faz-se o mesmo com uma função [async02] que devolve o seu resultado ao fim de 2 segundos através de um evento de temporizador. A função [async02] aceita dois parâmetros que são utilizados no seu resultado na linha 44;
  • quando forem chamadas, as duas funções [async01, async02]:
    • serão executadas;
    • devolverão ao código chamador duas promessas [promise1, promise2];
    • a execução voltará então ao código chamador, que continuará a sua execução;
    • após cerca de 1 segundo, a função [async01] emitirá um evento a indicar que concluiu o seu trabalho. O evento em questão será colocado em espera no ciclo de eventos associado ao resultado transmitido pela função [async01] juntamente com o evento;
    • após cerca de 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 um array passado como parâmetro ao método [Promise.all]. Sabe-se que ambas as funções [async01, async02] devolvem uma promessa ao código chamador. Assim, o parâmetro de [Promise.all] é um array 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] do array de parâmetros do método [all] passarem doestado [pending] para o estado [resolved]. Por outras palavras, a função [f1] será executada quando todas as promessas da matriz tiverem sido concluídas com sucesso;
    • a função [f2] do método [catch] será executada assim que uma das promessas da matriz passar do estado [pending] para o estado [rejected]. Por outras palavras, a função [f2] é executada assim que uma das promessas da 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 será 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. Funcionam em paralelo. Não é a execução do seu código que ocorre em paralelo, mas sim as respetivas esperas pelos dados solicitados decorrem simultaneamente;
  • linha 5: o código principal do script está concluído. Resta aguardar a conclusão das duas tarefas assíncronas [async01, async02];
  • linha 6: a tarefa assíncrona [async01] termina cerca de 1 s após o seu lançamento. Esta devolve um resultado através da função [resolve], pelo que a sua promessa na tabela da linha 56 do código passa 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] termina cerca de 2 s após o seu início. Esta devolve um resultado através da função [resolve], pelo que a sua promessa na tabela da linha 56 do código passa do estado [pending] para [resolved]. O método [then] será executado assim que o ciclo de eventos o permitir;
  • linha 8: o método [then] de [Promise.all] é executado. Recebe como parâmetro um array [result1, result2], em que [result1] é o resultado emitido por [async01], e [result2] é o resultado emitido por [async02];
  • linha 9: o método [finally] de [Promise.all] é executado;

11.6. script [async-06]

Este novo script mostra como a utilização conjunta das palavras-chave [async / await] permite obter um código assíncrono semelhante a um código síncrono. A gestão de eventos fica totalmente oculta e a compreensão do código é facilitada.

Retomamos o exemplo anterior, introduzindo as seguintes alterações:

  • adicionamos uma terceira função assíncrona [async03], que devolve o seu resultado através do método [Promise.reject], o qual, por sua vez, sinaliza ao ciclo de eventos que «falhou» na execução da sua tarefa;
  • executamos sequencialmente as três funções assíncronas [async01, async02, async03]. No exemplo anterior, tínhamos executado em paralelo as funções assíncronas [async01, async02];
  • antes do aparecimento das palavras-chave [async/await], a execução sequencial de ações assíncronas era feita com a ajuda de [Promise] aninhadas umas nas outras. Sempre que havia várias ações assíncronas a executar desta forma, o número de promessas aumentava em consequência e o código tornava-se menos legível;
  • com as palavras-chave [async/await], a execução sequencial de tarefas assíncronas é feita com uma sintaxe semelhante à da execução de tarefas síncronas:

// função assíncrona - utilização de async/await
async function main() {
  // execução sequencial de tarefas assíncronas
  try {
    // execução com espera por [async01]
    const result1 = await async01(...);
    console.log("[async01 result]=", result1);
    // execução com espera por [async02]
    const result2 = await async02(...);
    console.log("[async02 result]=", result2);
    // execução com espera por [async03]
    const result3 = await async03(...);
    console.log("[async03 result]=", result3);
  } catch (error) {
    // uma das ações assíncronas falhou
    console.log(sprintf("[sequential error]= %j, %s", error));
  } finally {
    // concluído
    console.log("[fin exécution séquentielle des tâches asynchrones],");
  }
  • linha 6: a função assíncrona [async01] é iniciada (palavra-chave await) e aguarda-se que ela publique o seu resultado através de um dos métodos [Promise.resolve, Promise.reject]. Trata-se, portanto, de uma operação bloqueante;
  • linha 6: a palavra-chave [await] transforma a operação assíncrona [async01] numa operação bloqueante. Sabe-se que a operação [async01] devolve um resultado de duas formas:
    • retorna ao código chamador, quase imediatamente, um objeto [Promise];
    • publica posteriormente um resultado no ciclo de eventos através dos métodos [Promise.resolve, Promise.reject]. É este último resultado que a [result1] recupera, na linha 6. A gestão de eventos da ação [async01] tornou-se invisível;
    • se o resultado [result] de [async01] for publicado por [Promise.resolve(result)], é atribuído a [result1], linha 6, e a execução continua na linha 7;
    • Se o resultado de [async01] for publicado por [Promise.reject], isso provoca uma exceção e a execução do código passa para a linha 14, a do bloco catch. O parâmetro da cláusula [catch] é o objeto de erro (error) publicado por [async01] com uma expressão [Promise.reject(error)]. A tarefa assíncrona também pode publicar o erro através de um [throw(error)]. O objeto [error] é aquele recuperado em [catch(error)];
    • a palavra-chave [await] deve obrigatoriamente constar numa função precedida pela palavra-chave [async], na 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 chamador;
  • repete-se o mesmo procedimento para a ação assíncrona [async02], linha 9, e [async03], linha 12;

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


try {
    // execução paralela das tarefas assíncronas
    const result = await Promise.all([async01(...), async02(...), async03(...)]);
    console.log(sprintf("[parallel success], %s, result=%j", heure(débutParallel), result));
  } catch (error) {
    // uma das ações assíncronas falhou
    console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
  } finally {
    // concluído
    console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
}
  • linha 3: temos uma operação bloqueante: aguarda-se que as três tarefas assíncronas da tabela [async01(..), async02(..), async03(..)] tenham publicado os seus resultados no ciclo de eventos com um dos métodos [Promise.resolve, Promise.reject];
  • se as três tarefas assíncronas publicarem os seus resultados com [Promise.resolve], a constante [result] é então a matriz [result1, result2, result3], em que:
    • [result1] é o resultado publicado por [async01] com a expressão [Promise.resolve(result1)];
    • [result2] é o resultado publicado por [async02] com a expressão [Promise.resolve(result2)];
    • [result3] é o resultado publicado por [async03] com a expressão [Promise.resolve(result3)];
  • se uma das três tarefas publicar o seu resultado com a expressão [Promise.reject(error)], ocorre uma exceção;
    • a constante [result] da linha 3 não recebe o seu valor;
    • a execução passa diretamente para [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, é possível executar tarefas assíncronas tanto de forma sequencial como paralela, tudo isto com uma sintaxe semelhante à de um código síncrono. Por isso, deve-se dar preferência a esta sintaxe, muito mais legível do que as anteriores. Esta sintaxe [async / await] só está disponível a partir da versão 6 do ECMAScript. Ainda existem muitos códigos JavaScript que utilizam promessas [Promise]. É por isso que é importante compreender também o seu funcionamento.

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


'use strict';

// execução paralela ou sequencial de várias tarefas assíncronas
// com as palavras-chave async / await

// importações
import moment from 'moment';
import { sprintf } from 'sprintf-js';

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

// uma função assíncrona que devolve um [Promise]
function async01(débutAsync01) {
  return new Promise(function (resolve) {
    console.log("[début fonction asynchrone async01],", heure());
    // função assíncrona
    setTimeout(function () {
      console.log("[fin fonction asynchrone async01],", heure(débutAsync01));
      // a ação assíncrona pode devolver um resultado complexo
      // aqui, sucesso
      resolve({
        prop1: [11, 21, 31],
        prop2: "abcd"
      });
    }, 1000)
  });
}

// uma função assíncrona que devolve um [Promise]
function async02(débutAsync02) {
  console.log("[début fonction asynchrone async02],", heure());
  return new Promise(function (resolve) {
    // função assíncrona
    setTimeout(function () {
      console.log("[fin fonction asynchrone async02],", heure(débutAsync02));
      // a ação assíncrona pode produzir um resultado complexo
      // aqui, sucesso
      resolve({
        prop1: [12, 22, 32],
        prop2: "xyzt"
      });
    }, 2000)
  })
}

// uma função assíncrona que devolve um [Promise]
function async03(débutAsync03) {
  console.log("[début fonction asynchrone async03],", heure());
  return new Promise((resolve, reject) => {
    // função assíncrona
    setTimeout(function () {
      console.log("[fin fonction asynchrone async03],", heure(débutAsync03));
      // a ação assíncrona pode produzir um resultado complexo
      // falha aqui
      reject({
        prop1: [13, 23, 33],
        prop2: "échec"
      });
    }, 3000)
  })
}

// função assíncrona — utilização de async/await
async function main() {
  const débutSequential = moment(Date.now());
  // execução sequencial de tarefas assíncronas
  console.log("------------ exécution séquentielle des tâches asynchrones lancée ------------------------")
  try {
    // execução com espera por [async01]
    const débutAsync01 = moment(Date.now());
    const result1 = await async01(débutAsync01);
    console.log("[async01 result]=", result1);
    // execução com espera por [async02]
    const débutAsync02 = moment(Date.now());
    console.log("début async02-------------", heure());
    const result2 = await async02(débutAsync02);
    console.log("[async02 result]=", result2);
    // execução com espera por [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) {
    // uma das ações assíncronas falhou
    console.log(sprintf("[sequential error]= %j, %s", error, heure(débutSequential)));
  } finally {
    // concluído
    console.log("[fin exécution séquentielle des tâches asynchrones],", heure(débutSequential));
  }

  const débutParallel = moment(Date.now());
  // execução em paralelo das tarefas assí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) {
    // uma das ações assíncronas falhou
    console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
  } finally {
    // concluído
    console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
  }

  // concluído
  console.log("[fin de la fonction main],", heure(débutSequential));
}
// execução da função assíncrona principal
main();

// será exibido antes das várias mensagens das funções assíncronas e dos respetivos eventos
console.log("[fin du code principal du script],", heure(débutScript));

// utilitário
function heure(début) {
  // hora atual
  const now = moment(Date.now());
  // formatação da 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);
    // formatação da duração
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // resultado
  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