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

Uma função assíncrona é aquela 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 seu resultado por meio dele.
Esse modo de funcionamento é bastante adequado para a execução em um navegador da web. De fato, um aplicativo executado em um navegador é um aplicativo baseado em eventos: o aplicativo reage a eventos, principalmente aqueles provocados pelo usuário (cliques, movimentos do mouse, digitação de texto, etc.). As aplicações JavaScript executadas em um navegador precisam interagir com serviços externos por meio do protocolo HTTP. As funções nativas HTTP do JavaScript são assíncronas: elas são iniciadas e a obtenção da resposta do serviço externo solicitado é sinalizada por um evento que se soma ao conjunto de eventos gerenciados pelo aplicativo.
Os scripts a seguir 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:
- [node.js], assim 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 até que essas tarefas assíncronas sejam concluídas. Elas emitem um evento quando são concluídas. Esses eventos são colocados em fila no ciclo de eventos;
- o script principal deve se inscrever para receber esses eventos se quiser recuperar os resultados das ações assíncronas;
- o script só é concluído quando todos os eventos emitidos por ele tiverem sido processados;
11.1. script [async-01]
O script a seguir mostra o comportamento de um script que contém 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 retorna imediatamente o número desse temporizador
// quando o temporizador atinge os 1000 ms, ele emite um evento que é colocado na fila do runtime
// quando o evento é processado pelo runtime, a função (primeiro parâmetro) é executada
setTimeout(function () {
// esse 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 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 de 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: registra-se a hora de início do script;
- linha 9: exibe-se essa hora usando o método [heure] das linhas 20 a 34;
- linhas 14 a 17: a função [setTimeout] tem dois parâmetros (f, duração): f é uma função que é executada quando [durée] milissegundos se passaram;
- linha 14: ao executar o script, a função [setTimeout] é executada:
- linha 17: um temporizador de 1.000 ms é acionado e a contagem regressiva começa até atingir 0 posteriormente. A função [setTimeout] é encerrada assim que o temporizador é inicializado e a contagem regressiva iniciada. Ela não aguarda o término dessa contagem regressiva. Ela retorna 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 ao 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 ao final do intervalo de 1.000 ms. A mensagem da linha 16 será então exibida;
- linha 20: essa 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 é a hora de início de uma operação cuja duração ela deve exibir;
- 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: esse número de milissegundos é decomposto em segundos e milissegundos;
- linha 34: a duração é somada à 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, vemos que a ação assíncrona [setTimeOut] foi concluída cerca de 1 s após o término do código principal do script;
- linha 6: a hora exibida na linha 3 é a da conclusão do código principal. Se este tiver iniciado tarefas assíncronas, o script só será concluído quando todas as tarefas assíncronas tiverem sido executadas. A duração exibida na linha 6 é a duração total de execução do script (código principal + tarefas assíncronas);
A função [setTimeout] nos permitirá simular tarefas assíncronas em um ambiente [node.js]. De fato, a função [setTimeout] se comporta como uma tarefa assíncrona:
- ela retorna um resultado imediatamente, neste caso um número de temporizador, por meio do mecanismo usual das funções (return);
- ela pode retornar posteriormente (o que ainda não é o caso acima) outros resultados por meio de eventos que são então processados pelo ciclo de eventos de [node.js];
- na maioria dos casos a seguir, esses eventos serão dois:
- um evento que poderíamos chamar de [success], que será emitido pela tarefa assíncrona que concluiu com sucesso o que deveria fazer. Um dado, o resultado da tarefa, está associado ao evento emitido;
- um evento que poderíamos chamar de [failure], que será emitido pela tarefa assíncrona que falhou ao realizar o que deveria fazer. Um dado — geralmente um objeto que descreve o erro — está associado ao evento emitido. Erros possíveis, por exemplo, em uma tarefa assíncrona de internet seriam “rede indisponível”, “servidor inexistente”, “tempo limite excedido”, etc.
- o código principal que iniciou uma tarefa assíncrona pode se inscrever para receber os eventos que essa tarefa possa 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. Essa 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 aos códigos que se inscreverem para recebê-los.
O acesso aos eventos de [node.js] requer bibliotecas adicionais. Escolhemos a biblioteca [events], que instalamos junto com [npm]:

O script [async-02] é o seguinte:
'use strict';
// as funções assíncronas podem retornar um resultado emitindo um evento
// o código principal pode recuperar esses resultados assinando 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 aciona um temporizador de 1000 ms (2º parâmetro) e retorna imediatamente o número desse temporizador
// quando o temporizador atinge os 1000 ms, ele emite um evento que é colocado na fila do runtime
// quando o evento é processado pelo runtime, a função (primeiro parâmetro) é executada
setTimeout(function () {
// esse 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 um resultado está disponível
eventEmitter.emit("timer1Success", { success: 4 });
// é emitido outro evento para indicar que outro resultado está disponível
eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)
// assina-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)));
});
// assina-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 de hora e 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 de 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, havíamos importado apenas objetos literais e funções;
- linha 15: cria-se um emissor de eventos [node.js] ao instanciar a classe [EventEmitter] com a palavra-chave [new];
- linhas 20-27: a função assíncrona [setTimeout]. Ela emitirá dois eventos durante 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 quantos eventos quiser. Mencionamos anteriormente que, na maioria das vezes, ela emite um dos dois eventos [success, failure], e não os dois, como fazemos aqui;
- linha 20: a execução de [setTimeout] é instantânea: um temporizador é acionado e seu número é retornado 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 nenhum código para processá-los quando ocorrerem. É por isso que o código principal deve se inscrever nos dois eventos [timer1Success, timer1Failure] se quiser gerenciá-los, especialmente para recuperar os dados associados a esses eventos;
- linhas 30-32: o código principal se inscreve no evento [timer1Success]. Quando o canal de eventos de [node.js] processar esse evento, ele chamará a função que é o segundo parâmetro do método [eventEmitter.on], passando a ela os dados (aqui denominados [result]) associados ao evento [timer1Success];
- linha 31: a função de processamento 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 se inscreve no evento [timer1Failure];
- A inscrição em um evento (1º parâmetro) não executa imediatamente o código da função [callback] (2º parâmetro). Essa função só será executada após a realização do evento;
- linha 40: o código principal do script foi concluído, mas não o script em si, uma vez que o código principal iniciou uma tarefa assíncrona. O script global só será 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, 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 depois do evento [‘timer1Success’];
- linha 8: fim do script global com duração total de 1,627 segundo;
11.3. script [async-03]
O script a seguir mostra outro aspecto do ciclo de eventos de [node.js]:
- o ciclo executa os eventos um após o outro, geralmente na 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 loop executa apenas um evento por vez. O próximo só é processado quando o processamento do anterior estiver concluído. Em um sistema baseado em eventos, deve-se, portanto, evitar escrever código que monopolize o processador por muito tempo, pois, nesse caso, os eventos não são processados quando ocorrem, mas sim mais tarde, quando o loop de eventos chega até eles. O resultado é um aplicativo pouco “responsivo”;
O script [async-03] mostra um exemplo desse fenômeno:
'use strict';
// as funções assíncronas podem retornar um resultado emitindo um evento
// o código principal pode recuperar esses resultados assinando 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 aciona um temporizador de 1000 ms (2º parâmetro) e retorna imediatamente o número desse temporizador
// quando o temporizador atinge os 1000 ms, ele emite um evento que é colocado na fila do runtime
// quando o evento é processado pelo runtime, a função (1º parâmetro) é executada
setTimeout(function () {
// esse 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 um resultado está disponível
eventEmitter.emit("timer1Success", { success: 4 });
// é emitido outro evento para indicar que outro resultado está disponível
eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)
// assina-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)));
});
// assina-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 pesado que impediu que o código principal fosse concluído antes do término 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 para exibição de hora e duração
function heure(début) {
...
}
Comentários
- esse código é o do exemplo anterior, [async-02], ao qual foram adicionadas as linhas 39 a 44;
- linhas 20 a 27: a função [setTimeout] foi programada para executar uma função assíncrona interna após um intervalo de um segundo. Após esse segundo, a execução da função assíncrona do temporizador não ocorre imediatamente: um evento é inserido no ciclo de execução para solicitá-la. Se o ciclo de execução estiver ocupado processando outro evento, a execução da função assíncrona do temporizador terá que aguardar;
- linhas 20-27: assim que a função [setTimeout] armou seu temporizador com um intervalo de um segundo, ela libera o processador e devolve o controle ao código chamador. Este continua com as linhas 30-37, que são assinaturas de eventos e têm um tempo de execução insignificante;
- o código principal continua com as linhas 40-44, que formam um loop de 1.010 iterações. Esse código estará em execução quando o temporizador emitir seu evento de “fim do intervalo de 1 segundo”. Esse evento é então colocado no loop de eventos, mas terá que aguardar o término da execução do código principal do script para ter a chance de ser processado;
- linha 47: fim do código principal do script. É após essa ú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: vemos que o código principal do script levou 9 segundos para ser executado. Os eventos que puderam ocorrer durante esse tempo foram colocados em espera no loop de eventos;
- linha 4: vemos que o evento [fin du timer] foi processado 5 ms após o término do código principal. Ele foi emitido cerca de 1 s após o início do script, mas teve que esperar mais 8 s para ser finalmente processado;
O que podemos concluir deste exemplo é que, em um sistema baseado em eventos, um código nunca deve ocupar o processador por muito tempo. Se tivermos um código síncrono demorado para ser executado, devemos “dar um jeito” de dividi-lo em tarefas assíncronas mais curtas, que sinalizarão sua conclusão por meio de um evento.
11.4. script [async-04]
O script [async-04] mostra outro mecanismo, chamado [Promise], uma promessa de resultado. Esse mecanismo evita o gerenciamento explícito de eventos [node.js]. Isso é feito implicitamente, e o desenvolvedor pode, assim, ignorar a existência desses eventos. No entanto, compreendê-los permitirá que ele entenda melhor o funcionamento dos [Promise], que à primeira vista parece complexo.
O tipo [Promise] é uma classe JavaScript. Seu construtor aceita como parâmetro uma função assíncrona, à qual passa dois parâmetros tradicionalmente chamados de [resolve] e [reject]. Eles poderiam ter outro nome;
- o construtor de [Promise] realiza duas ações:
- ele 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 seu resultado e retorna imediatamente um objeto [Promise] ao código chamador. Esse objeto pode apresentar quatro estados:
- [pending]: a ação assíncrona que gerou a [Promise] ainda não foi concluída;
- [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] foi concluída com falha;
- [settled]: a ação assíncrona que gerou a [Promise] foi concluída;
- ele 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 seu resultado e retorna imediatamente um objeto [Promise] ao código chamador. Esse objeto pode apresentar quatro estados:
Quando o construtor retorna seu resultado, o objeto [Promise] criado está no estado [pending], aguardando os resultados da função assíncrona;
- (continuação)
- a tarefa assíncrona das linhas 2 a 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:
- execução de um código síncrono para iniciar a operação de E/S com outro dispositivo, por exemplo, um servidor remoto;
- aguardar a resposta desse dispositivo;
- processamento dessa resposta;
- a tarefa assíncrona das linhas 2 a 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:
É a fase 2, de espera pelo dispositivo externo ao processador, que é a mais dispendiosa. Em vez de esperar:
- a recepçã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), vamos nos inscrever para receber esse evento e, em determinado momento, retornar ao ciclo de eventos de [node.js]. O evento seguinte na lista de eventos em espera será então processado;
- durante a fase 2, há paralelismo de execução, mas em dispositivos diferentes:
- o processador para o ciclo de eventos;
- um dispositivo externo (disco, banco de dados, servidor remoto) para a busca dos dados solicitados;
- no final da fase 2, quando a operação de E/S tiver obtido os dados solicitados, um evento será emitido para indicar que o resultado da E/S está disponível. Esse evento passará então a integrar-se aos demais na fila de eventos;
- quando chegar a sua vez, ele será processado. A função associada a esse evento (linha 7 do código de exemplo) será então executada;
Esse modo de funcionamento permite evitar tempos ociosos: 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 seu trabalho, ela poderá retornar um resultado ao código chamador, graças às duas funções [resolve, reject] que o construtor [Promise] passou a ela como parâmetros. A convenção é a seguinte:
- a tarefa assíncrona sinaliza sucesso por meio de [resolve(result)]. Isso equivale a inserir no ciclo de eventos de [node.js] um evento que poderíamos chamar de [resolved], com [result] como dado associado;
- a tarefa assíncrona sinaliza uma falha por meio de [reject(error)]. Isso equivale a inserir no ciclo de eventos de [node.js] um evento que poderíamos chamar de [rejected], com [error] como dado associado — geralmente um objeto que detalha o erro ocorrido;
- portanto, o código chamador precisa se inscrever para receber esses dois eventos a fim de ser notificado sobre a disponibilidade do resultado da função assíncrona;
- quando a tarefa assíncrona das linhas 2 e 5 for iniciada e concluir seu trabalho, ela poderá retornar um resultado ao código chamador, graças às duas funções [resolve, reject] que o construtor [Promise] passou a ela como parâmetros. A convenção é a seguinte:
Após a conclusão da execução da tarefa assíncrona encapsulada no [Promise], o estado do objeto [promise] retornado pelo construtor [Promise(…)] muda:
- 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 assinatura dos eventos [resolved] e [rejected] da tarefa assíncrona é feita por meio 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 seu trabalho com sucesso. Ela 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 ao realizar sua tarefa. Ela 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], portanto, sempre executada. Ela não recebe nenhum parâmetro;
Essa sintaxe oculta completamente os eventos aos quais estamos assinados. No entanto, trata-se de uma assinatura e, assim como no exemplo anterior, ela 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 estamos assinados.
O script [async-04] ilustra esse mecanismo:
'use strict';
// é possível obter os resultados (sucesso, falha) de uma função assíncrona
// sem usar eventos explicitamente, graças à classe [Promise]
// essa 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 usando 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) {
// registro
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 retorna um resultado com a função [resolve]
// a promessa é então cumprida
resolve('[réussite]');
}, 1000)
});
// é possível obter o resultado da promessa [promise1]
// quando esta tiver sido resolvida (resolve) ou rejeitada (reject)
// a instrução a seguir é uma assinatura do evento [resolved] por meio do método [then]
// e ao evento [rejected] por meio do método [catch]
// o método [finally] é executado, 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 usando uma promessa [Promise]
const débutPromise2 = moment(Date.now());
const promise2 = new Promise(function (resolve, reject) {
// registro
console.log("[début fonction asynchrone de promise2],", heure(débutPromise1));
// tarefa assíncrona
setTimeout(function () {
console.log("[fin fonction asynchrone de promise2],", heure(débutPromise2));
// a tarefa assíncrona retorna um resultado com a função [reject]
// a promessa é então descumprida
reject('[échec]');
}, 2000)
});
// é possível saber o resultado da promessa [promise2]
// quando ela for 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 mensagens 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 de 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]. Sua função assíncrona retorna o resultado por meio de um evento após um segundo. Uma vez iniciada essa operação assíncrona (ativação de um temporizador), não se aguarda o retorno do resultado e passa-se imediatamente para o código da linha 35;
- linhas 35-44: assinamos os dois eventos [resolved, rejected] que a função assíncrona interna ao [promise1] pode emitir;
- linhas 46-71: repete-se a mesma sequência de código da anterior para uma segunda promessa [promise2];
- linha 74: o código principal do script está concluído, mas não o script como um todo, pois duas ações assíncronas foram iniciadas. Retornamos ao ciclo de eventos, onde, em algum momento, um dos eventos [resolved, rejected] das promessas [promise1, promise2] ocorrerá. Ele será então processado;
- em seguida, haverá um retorno 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 sua conclusão, que será sinalizada por um evento;
- linha 4: a função assíncrona de [promise2] é iniciada, mas não se aguarda sua conclusão, que será sinalizada por um evento;
- linha 5: fim do código principal e retorno 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: como o [promise2] ainda não concluiu seu trabalho, o evento [promise1 resolved], que acaba de ser inserido 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 [promise2] passa do estado [pending] para [resolved];
11.5. script [async-05]
Voltemos ao código do construtor de um objeto [Promise]:
Na linha 2, a tarefa assíncrona do [Promise] é iniciada. Ela frequentemente precisa de mais parâmetros do que apenas os parâmetros [resolve, reject] que a função que a encapsula lhe passa. Nesse caso, encapsula-se a criação da [Promise] em uma função que passará a ela os parâmetros de que sua função assíncrona necessita:
O script a seguir:
- define duas funções assíncronas que retornam um [Promise];
- inicia sua execução em paralelo e aguarda que ambas sejam concluídas para realizar uma determinada tarefa;
'use strict';
// é possível definir funções assíncronas que retornam um tipo [Promise]
// e, então, podem 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 retorna 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 retornar um resultado complexo
resolve({
prop1: [10, 20, 30],
prop2: "abcd",
prop3: p1,
});
}, 1000)
});
}
// uma função assíncrona que retorna 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 retornar 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 até que ambas tenham sido concluídas
// o then só é executado se ambas as funções tiverem disparado o evento [resolved]
// o `catch` é executado assim que uma das duas funções dispara o evento [rejected]
Promise.all([async01(10), async02(10, 20)])
//; o resultado é uma matriz [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));
})
// error é 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));
})
// 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 de 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 retorna seu resultado após 1 segundo por meio 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 retorna seu resultado após 2 segundos por meio de um evento de temporizador. A função [async02] aceita dois parâmetros, que são utilizados no resultado da linha 44;
- quando forem chamadas, as duas funções [async01, async02]:
- serão executadas;
- retornarão ao código chamador duas promessas [promise1, promise2];
- a execução retornará então ao código chamador, que continuará sua execução;
- após cerca de 1 segundo, a função [async01] emitirá um evento para indicar que concluiu seu trabalho. O evento em questão será colocado em espera no ciclo de eventos associado ao resultado transmitido pela função [async01] junto com o evento;
- após cerca de 2 segundos, o mesmo processo ocorrerá para [async02];
- linha 54: só agora as funções assíncronas [async01, async02] são executadas (notações async01(10) e async02(10,20)). Elas são executadas dentro de uma matriz passada como parâmetro ao método [Promise.all]. Sabe-se que ambas as funções [async01, async02] retornam uma promessa ao código chamador. Portanto, o parâmetro de [Promise.all] é uma matriz com duas promessas;
- [Promise.all([promise1, promise2, …, promisen]).then(f1).catch(f2).finally(f3)] é uma assinatura de eventos:
- [Promise.all] é do tipo [Promise];
- a função [f1] do método [then] será executada quando todas as promessas [promise1, promise2, …, promisen] da matriz de parâmetros do método [all] passarem doestado [pending] para o estado [resolved]. Em outras palavras, [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]. Em outras palavras, [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], portanto, 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 funcionam em paralelo. Não é a execução de seus códigos que ocorre em paralelo, mas suas respectivas espera pelos dados solicitados acontecem ao mesmo tempo;
- linha 5: o código principal do script foi concluído. Resta aguardar o término das duas tarefas assíncronas [async01, async02];
- linha 6: a tarefa assíncrona [async01] é concluída cerca de 1 s após seu início. Ela retorna um resultado com a função [resolve]; portanto, sua promessa na tabela da linha 56 do código passa do estado [pending] para [resolved]. Isso não é suficiente para acionar o método [then], linhas 59-60 do código;
- linha 7: a tarefa assíncrona [async02] é concluída cerca de 2 s após sua execução. Ela retorna um resultado com a função [resolve]; portanto, 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 permitir;
- linha 8: o método [then] de [Promise.all] é executado. Ele 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 o uso conjunto das palavras-chave [async / await] permite obter um código assíncrono semelhante a um código síncrono. O gerenciamento de eventos fica totalmente oculto, facilitando a compreensão do código.
Retomamos o exemplo anterior, introduzindo as seguintes modificações:
- adicionamos uma terceira função assíncrona [async03], que retorna seu resultado por meio do método [Promise.reject], o que, por sua vez, sinaliza ao ciclo de eventos que ela “falhou” ao realizar 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 surgimento das palavras-chave [async/await], a execução sequencial de ações assíncronas era feita por meio de [Promise] aninhadas umas nas outras. Sempre que havia várias ações assíncronas a serem executadas dessa forma, o número de promessas aumentava proporcionalmente e o código se tornava 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 — uso 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 seu resultado por meio 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] em uma operação bloqueante. Sabe-se que a operação [async01] retorna um resultado de duas maneiras:
- ela retorna ao código chamador, quase imediatamente, um objeto [Promise];
- posteriormente, publica um resultado no ciclo de eventos por meio dos métodos [Promise.resolve, Promise.reject]. É esse último resultado que [result1] recupera, na linha 6. O gerenciamento de eventos da ação [async01] tornou-se invisível;
- se o resultado [result] de [async01] for publicado por [Promise.resolve(result)], ele é atribuído a [result1] na linha 6 e a execução continua na linha 7;
- Se o resultado de [async01] for publicado por [Promise.reject], isso gera uma exceção e a execução do código passa para a linha 14, onde está o 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 por meio de um [throw(error)]. O objeto [error] é aquele recuperado em [catch(error)];
- a palavra-chave [await] deve estar obrigatoriamente em uma função precedida pela palavra-chave [async], na linha 2. Essa 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 retorne um objeto [Promise] ao código chamador;
- fazemos o mesmo 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: aguardamos que as três tarefas assíncronas da matriz [async01(..), async02(..), async03(..)] tenham publicado seus resultados no ciclo de eventos com um dos métodos [Promise.resolve, Promise.reject];
- se as três tarefas assíncronas publicarem seus resultados com [Promise.resolve], a constante [result] será 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 seu resultado com a expressão [Promise.reject(error)], ocorrerá uma exceção;
- a constante [result] da linha 3 não recebe seu valor;
- a execução passa diretamente para [catch] da linha 5;
- o parâmetro (error) do catch é o objeto (error) publicado pela expressão [Promise.reject(error)];
Ao combinar essas duas sintaxes, é possível executar tarefas assíncronas tanto de forma sequencial quanto paralela, tudo isso com uma sintaxe semelhante à de um código síncrono. Portanto, deve-se dar preferência a essa sintaxe, que é muito mais legível do que as anteriores. Essa sintaxe [async / await] só está disponível a partir da versão 6 do ECMAScript. Ainda há muitos códigos JavaScript que utilizam promessas [Promise]. É por isso que também é importante compreender o funcionamento delas.
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 retorna 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 retornar um resultado complexo
// aqui, sucesso
resolve({
prop1: [11, 21, 31],
prop2: "abcd"
});
}, 1000)
});
}
// uma função assíncrona que retorna 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 gerar um resultado complexo
// aqui, sucesso
resolve({
prop1: [12, 22, 32],
prop2: "xyzt"
});
}, 2000)
})
}
// uma função assíncrona que retorna 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 gerar um resultado complexo
// falha aqui
reject({
prop1: [13, 23, 33],
prop2: "échec"
});
}, 3000)
})
}
// função assíncrona — uso 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 em espera de [async03]
const débutAsync03 = moment(Date.now());
console.log("début async03-------------", heure());
const result3 = await async03(débutAsync03);
console.log("[async03 result]=", result3);
} catch (error) {
// 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 paralela 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 main
main();
// será exibido antes das diversas mensagens das funções assíncronas e de seus 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 de 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