11. Подієве програмування та асинхронні функції

Асинхронна функція — це функція, виконання якої запускається, але результат якої не очікується. Коли виконання завершується, асинхронна функція генерує подію та передає свій результат через неї.
Цей режим роботи добре підходить для виконання у веб-браузері. Адже додаток, що виконується у браузері, є подієвим: додаток реагує на події, які здебільшого ініціює користувач (кліки, переміщення миші, введення тексту тощо). JavaScript-додатки, що виконуються в браузері, взаємодіють із зовнішніми сервісами через протокол HTTP. Вбудовані функції JavaScript HTTP є асинхронними: вони запускаються, а отримання відповіді від запитуваного зовнішнього сервісу сигналізується подією, яка додається до набору подій, що обробляються додатком.
Наступні скрипти будуть виконуватися [node.js], а не браузером. [node.js] також має режим виконання за подіями:
- [node.js], як і браузер, використовує цикл подій для виконання скрипту;
- виконання основного коду скрипта є першою подією, що виконується;
- якщо цей основний код запустив асинхронні завдання, то виконання триває доти, доки ці асинхронні завдання не завершаться. Вони генерують подію після завершення. Ці події ставляться в чергу в циклі подій;
- головний скрипт повинен підписатися на ці події, якщо він хоче отримати результати асинхронних дій;
- скрипт вважається завершеним лише після обробки всіх подій, які він генерував;
11.1. скрипт [async-01]
Наступний скрипт ілюструє поведінку скрипта, що містить асинхронну дію.
'use strict';
// імпортні модулі
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// початок
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// setTimeout запускає таймер на 1000 мс (2-й параметр) і негайно повертає номер цього таймера
// коли таймер відлічує 1000 мс, він генерує подію, яка ставиться в чергу середовища виконання
// коли подія обробляється середовищем виконання, виконується функція (1-й параметр)
setTimeout(function () {
// цей код буде виконано, коли таймер досягне значення 0
console.log("[fin de l'action asynchrone setTimeout],", heure(débutScript));
}, 1000)
// з'явиться перед повідомленням внутрішньої функції таймера
console.log("[fin du code principal du script],", heure(débutScript));
// утиліта для відображення часу та тривалості
function heure(début) {
// поточний час
const now = moment(Date.now());
// форматування часу
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);
// форматування часу + тривалості
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// результат
return result;
}
- рядок 4: імпортується бібліотека [moment] для форматування дат (рядок 27);
- рядок 5: імпортується бібліотека [sprintf-js] для форматування тривалості (рядок 34);
- рядок 8: фіксуємо час початку виконання скрипта;
- рядок 9: виводимо її за допомогою методу [heure] у рядках 20–34;
- рядки 14–17: функція [setTimeout] має два параметри (f, тривалість): f — це функція, яка виконується, коли минуло [durée] мілісекунд;
- рядок 14: під час виконання скрипта виконується функція [setTimeout]:
- рядок 17: запускається таймер на 1000 мс, і починається відлік до досягнення значення 0. Функція [setTimeout] завершується, щойно таймер ініціалізовано та розпочато відлік. Вона не чекає на завершення цього відліку. Вона повертає номер, що ідентифікує використаний таймер, і виконання переходить до наступної інструкції, рядка 20. Тут результат функції [setTimeout] не використовується;
- рядок 16: це повідомлення з’явиться на екрані після закінчення 1000 мс функції [setTimeout];
- рядки 15–16: функція f, перший параметр функції [setTimeout], буде виконана після закінчення 1000 мс. Після цього з’явиться повідомлення з рядка 16;
- рядок 20: це повідомлення з’явиться на екрані раніше, ніж повідомлення з рядка 16;
Функція часу:
- рядок 23: функція приймає необов’язковий параметр [heure], який є часом початку операції, тривалість якої вона має відобразити;
- рядки 25–27: обчислюється та форматується поточний час;
- рядок 29: якщо параметр [début] присутній, то потрібно обчислити тривалість;
- рядок 30: тривалість операції. Отримуємо число в мілісекундах;
- рядки 31–32: це число мілісекунд розкладається на секунди та мілісекунди;
- рядок 34: тривалість додається до часу;
Виконання
[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
- у рядку 4 видно, що асинхронна дія [setTimeOut] завершилася приблизно через 1 секунду після завершення основного коду скрипта;
- рядок 6: час, що відображається в рядку 3, — це час завершення основного коду. Якщо він запустив асинхронні завдання, скрипт завершується лише після виконання всіх асинхронних завдань. Тривалість, що відображається в рядку 6, — це загальна тривалість виконання скрипта (основний код + асинхронні завдання);
Функція [setTimeout] дозволить нам імітувати асинхронні завдання в середовищі [node.js]. Справді, функція [setTimeout] поводиться як асинхронне завдання:
- вона негайно повертає результат — у даному випадку номер таймера — за допомогою стандартного механізму функцій (return);
- вона може згодом (у наведеному вище прикладі це ще не так) повернути інші результати через події, які потім обробляються циклом подій функції [node.js];
- у більшості випадків, що будуть розглянуті далі, таких подій буде дві:
- подія, яку можна назвати [success], яка буде згенерована асинхронним завданням, що успішно виконало те, що мало зробити. До згенерованої події прив’язується дане — результат завдання;
- подія, яку можна назвати [failure], яка буде згенерована асинхронним завданням, що не змогло виконати те, що мало зробити. З генерованою подією пов’язано дане, як правило, об’єкт, що описує помилку. Можливими помилками, наприклад, у разі асинхронного інтернет-завдання, можуть бути «мережа недоступна», «сервер не існує», «час очікування перевищено» тощо.
- головний код, який запустив асинхронне завдання, може підписатися на події, які це завдання може генерувати. Коли одна з таких подій генерується, головний код отримує відповідне повідомлення і може запустити виконання спеціальної функції, призначеної для обробки цієї події. Ця функція отримує як параметр дані, які асинхронне завдання пов’язало з генерованою подією;
11.2. скрипт [async-02]
У цьому скрипті асинхронна функція [setTimeout] буде генерувати події для передачі даних кодам, які підписалися на ці події.
Для доступу до подій [node.js] потрібні додаткові бібліотеки. Ми обираємо бібліотеку [events], яку встановлюємо разом із [npm]:

Скрипт [async-02] має такий вигляд:
'use strict';
// асинхронні функції можуть повертати результат, генеруючи подію
// головний код може отримувати ці результати, підписавшись на генеровані події
// імпорт
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';
// початок
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// генератор подій
const eventEmitter = new EventEmitter();
// setTimeout запускає таймер на 1000 мс (2-й параметр) і негайно повертає номер цього таймера
// коли таймер відлічив 1000 мс, він генерує подію, яка ставиться в чергу середовища виконання
// коли подія обробляється середовищем виконання, виконується функція (1-й параметр)
setTimeout(function () {
// цей код буде виконано, коли таймер досягне значення 0
console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
// генерується подія, що повідомляє про наявність результату
eventEmitter.emit("timer1Success", { success: 4 });
// генерується ще одна подія, що повідомляє про наявність іншого результату
eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)
// підписуємося на подію [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)));
});
// підписуємося на подію [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)));
});
// відображатиметься перед повідомленнями подій, що генеруються функцією, пов’язаною з [timer1]
console.log("[fin du code principal du script],", heure(débutScript));
// утиліта для відображення часу та тривалості
function heure(début) {
// поточний час
const now = moment(Date.now());
// форматування часу
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);
// форматування часу + тривалості
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// результат
return result;
}
Коментарі
- у рядку 9 імпортується клас [EventEmitter] із бібліотеки [events]. Це нововведення: до цього ми імпортували лише літеральні об’єкти та функції;
- рядок 15: створюється генератор подій [node.js] шляхом інстанціювання класу [EventEmitter] із ключовим словом [new];
- рядки 20–27: асинхронна функція [setTimeout]. Під час виконання вона генерує дві події:
- у рядку 24 — подія [timer1Success] із пов’язаним значенням об’єкта {success : 4};
- рядок 26 — подія [timer1Failure] із пов’язаним значенням об’єкта {failure : 6};
- асинхронна функція може генерувати скільки завгодно подій. Раніше зазначалося, що найчастіше вона генерує одну з двох подій [success, failure], а не обидві, як це робиться тут;
- рядок 20: виконання [setTimeout] відбувається миттєво: запускається таймер, а його номер повертається коду, що викликає. Генерація подій відбудеться пізніше, у даному випадку через 1 секунду;
- відправлення подій не має сенсу, якщо немає коду, який би їх обробляв, коли вони відбудуться. Саме тому головний код повинен підписатися на обидві події [timer1Success, timer1Failure], якщо він хоче їх обробляти, зокрема отримувати дані, пов’язані з цими подіями;
- рядки 30–32: основний код підписується на подію [timer1Success]. Коли канал подій [node.js] обробить цю подію, він викличе функцію, яка є другим параметром методу [eventEmitter.on], передавши їй дані (тут названі [result]), пов’язані з подією [timer1Success];
- рядок 31: функція обробки події виведе jSON даних, пов’язаних із подією, а також поточний час;
- рядки 35–37: за допомогою аналогічного коду головний код підписується на подію [timer1Failure];
- підписка на подію (1-й параметр) не призводить до негайного виконання коду функції [callback] (2-й параметр). Ця функція буде виконана лише після того, як подія відбудеться;
- рядок 40: основний код скрипта завершено, але сам скрипт ще не завершився, оскільки основний код запустив асинхронне завдання. Загальний скрипт завершиться лише після закінчення цього асинхронного завдання;
Саме це показують отримані результати:
[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
- рядок 3: кінець основного коду через 10 мс після початку виконання скрипта;
- рядок 4: початок функції, інкапсульованої в таймер на 1000 мс, приблизно через 1 секунду після початку виконання скрипта;
- рядок 5: обробка події [‘timer1Success’], через 2 мс;
- рядок 6: обробка події [‘timer1Failure’], через 1 мс після події [‘timer1Success’];
- рядок 8: завершення глобального скрипта з загальною тривалістю 1,627 секунди;
11.3. скрипт [async-03]
Наступний скрипт ілюструє інший аспект циклу подій [node.js]:
- цикл виконує події одну за одною, зазвичай у порядку їх надходження. Деякі OS надають пріоритети подіям, які потім обробляються за порядком пріоритету, а не за порядком надходження;
- цикл виконує лише одну подію за раз. Наступна обробляється лише після завершення обробки попередньої. Тому в подієвій системі слід уникати написання коду, який тривалий час монополізує процесор, оскільки в такому разі події обробляються не в момент їхнього виникнення, а пізніше, коли подієвий цикл до них дістанеться. У результаті ми отримуємо мало «реактивний» додаток;
Скрипт [async-03] демонструє приклад цього явища:
'use strict';
// асинхронні функції можуть повертати результат, генеруючи подію
// головний код може отримувати ці результати, підписавшись на генеровані події
// імпорт
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';
// початок
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// генератор подій
const eventEmitter = new EventEmitter();
// setTimeout запускає таймер на 1000 мс (2-й параметр) і негайно повертає номер цього таймера
// коли таймер відлічив 1000 мс, він генерує подію, яка ставиться в чергу середовища виконання
// коли подія обробляється середовищем виконання, виконується функція (1-й параметр)
setTimeout(function () {
// цей код буде виконано, коли таймер досягне значення 0
console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
// генерується подія, що повідомляє про наявність результату
eventEmitter.emit("timer1Success", { success: 4 });
// генерується ще одна подія, що повідомляє про наявність іншого результату
eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)
// підписуємося на подію [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)));
});
// підписуємося на подію [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)));
});
// дещо ресурсоємний синхронний код, який завадив завершенню виконання основного коду до закінчення [timer1]
for (let i = 0; i < 1000000; i++) {
for (let j = 0; j < 10000; j++) {
i + i ^ 2 + i ^ 3;
}
}
// відобразиться раніше, ніж повідомлення про події, що генеруються функцією, пов’язаною з [timer1]
console.log("[fin du script],", heure(débutScript));
// утиліта для відображення часу та тривалості
function heure(début) {
...
}
Коментарі
- цей код є кодом з попереднього прикладу [async-02], до якого додано рядки 39–44;
- рядки 20–27: функція [setTimeout] була запрограмована для виконання внутрішньої асинхронної функції через секунду. По закінченні цієї секунди виконання асинхронної функції таймера не відбувається одразу: у цикл виконання вставляється подія, щоб викликати його. Якщо цикл виконання зайнятий обробкою іншої події, виконання асинхронної функції таймера доведеться відкласти;
- рядки 20–27: щойно функція [setTimeout] запустила свій таймер із затримкою в одну секунду, вона звільняє процесор і повертає контроль коду, що її викликав. Останній продовжує виконання рядків 30–37, які є підписками на події та мають незначний час виконання;
- основний код продовжується рядками 40–44, які утворюють цикл із 1010 ітерацій. Цей код буде виконуватися в той момент, коли таймер згенерує подію «закінчення 1-секундної затримки». Ця подія потрапляє до циклу обробки подій, але повинна дочекатися завершення виконання основного коду скрипта, щоб мати шанс бути обробленою;
- рядок 47: кінець основного коду скрипта. Лише після цього останнього виведення на екран подія «закінчення таймера» зможе бути оброблена, і внутрішня асинхронна функція [setTimeout] зможе бути виконана;
Скрипт дає такі результати:
[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
Коментарі
- рядок 3: бачимо, що виконання основного коду скрипта зайняло 9 секунд. Події, які могли відбутися за цей час, були поставлені в чергу в циклі подій;
- рядок 4: бачимо, що подія [fin du timer] була оброблена через 5 мс після завершення основного коду. Вона була згенерована приблизно через 1 с після початку роботи скрипта, але мусила чекати ще 8 с, щоб бути нарешті обробленою;
З цього прикладу слід винести, що в системі, яка працює на основі подій, код ніколи не повинен займати процесор надто довго. Якщо у нас є синхронний код, виконання якого займає багато часу, потрібно «придумати», як розбити його на коротші асинхронні завдання, які сигналізуватимуть про своє завершення за допомогою події.
11.4. скрипт [async-04]
Скрипт [async-04] демонструє інший механізм, який називається [Promise] — обіцянка результату. Цей механізм дозволяє уникнути явного управління подіями [node.js]. Це відбувається неявно, і розробник може ігнорувати існування цих подій. Однак розуміння цих подій дозволить йому краще осягнути принцип роботи [Promise], який на перший погляд видається складним.
Тип [Promise] є класом JavaScript. Його конструктор приймає як аргумент асинхронну функцію, якій передає два параметри, що традиційно називаються [resolve] та [reject]. Вони могли б мати іншу назву;
- конструктор [Promise] виконує дві дії:
- він створює подію для запуску виконання функції [function(resolve, reject)], яку йому передали як параметр, але не чекає на її результат і негайно повертає об’єкт [Promise] коду, що викликає. Цей об’єкт може мати чотири стани:
- [pending]: асинхронна операція, яка викликала [Promise], ще не завершена;
- [fulfilled]: асинхронна операція, яка повернула [Promise], успішно завершилася;
- [rejected]: асинхронна операція, яка повернула [Promise], завершилася невдало;
- [settled]: асинхронна операція, яка повернула [Promise], завершилася;
- він створює подію для запуску виконання функції [function(resolve, reject)], яку йому передали як параметр, але не чекає на її результат і негайно повертає об’єкт [Promise] коду, що викликає. Цей об’єкт може мати чотири стани:
Коли конструктор повертає свій результат, створений об’єкт [Promise] перебуває у стані [pending], очікуючи на результати асинхронної функції;
- (продовження)
- асинхронне завдання з рядків 2–5 запускається негайно. Асинхронні завдання найчастіше є асинхронними завданнями вводу/виводу, які розкладаються наступним чином:
- виконання синхронного коду для запуску операції вводу-виводу з іншим пристроєм, наприклад, віддаленим сервером;
- очікування відповіді від цього пристрою;
- обробка цієї відповіді;
- асинхронне завдання з рядків 2–5 запускається негайно. Асинхронні завдання найчастіше є асинхронними завданнями вводу/виводу, які розкладаються наступним чином:
Саме фаза 2 — очікування відповіді від зовнішнього пристрою — є найбільш ресурсоємною. Замість очікування:
- отримання запитуваних даних від зовнішнього пристрою буде сигналізуватися подією;
- у синхронному коді, що йде після фази 1 (рядок 7 прикладу коду), ми підпишемося на цю подію, а потім у певний момент повернемося до циклу подій [node.js]. Після цього буде оброблено наступну подію у списку подій, що очікують на обробку;
- під час фази 2 відбувається паралельне виконання, але на різних периферійних пристроях:
- процесор — для циклу подій;
- зовнішній пристрій (диск, база даних, віддалений сервер) — для пошуку запитуваних даних;
- наприкінці фази 2, коли операція вводу-виводу отримала запитувані дані, буде згенеровано подію, що вказує на доступність результату вводу-виводу. Ця подія приєднається до інших у черзі подій;
- коли настане його черга, воно буде оброблено. Тоді буде виконано функцію, пов’язану з цією подією (рядок 7 прикладу коду);
Такий режим роботи дозволяє уникнути простоїв: коли процесор очікує відповіді від периферійного пристрою, що працює повільніше за нього;
- (продовження)
- коли асинхронне завдання з рядків 2 та 5 було запущено та завершило свою роботу, воно має можливість повернути результат коду, що його викликав, завдяки двом функціям [resolve, reject], які конструктор [Promise] передав йому як параметри. Умова така:
- асинхронне завдання сигналізує про успіх за допомогою [resolve(result)]. Це означає, що в цикл подій [node.js] додається подія, яку можна назвати [resolved], з [result] як пов’язаним даними;
- асинхронне завдання повідомляє про збій за допомогою [reject(error)]. Це еквівалентно додаванню до циклу подій [node.js] події, яку можна назвати [rejected], з [error] як пов’язаними даними — зазвичай це об’єкт, що детально описує помилку, яка сталася;
- отже, код, що викликає функцію, повинен підписатися на ці дві події, щоб отримувати сповіщення про доступність результату асинхронної функції;
- коли асинхронне завдання з рядків 2 та 5 було запущено та завершило свою роботу, воно має можливість повернути результат коду, що його викликав, завдяки двом функціям [resolve, reject], які конструктор [Promise] передав йому як параметри. Умова така:
Після завершення виконання асинхронного завдання, інкапсульованого в [Promise], стан об’єкта [promise], поверненого конструктором [Promise(…)], змінюється:
- подія [resolved] переводить його зі стану [pending] у стан [resolved];
- подія [rejected] переводить його зі стану [pending] у стан [rejected];
Підписка на події [resolved] та [rejected] асинхронного завдання здійснюється за допомогою методів класу [Promise] із таким синтаксисом:
promise.then(f1).catch(f2).finally(f3);
де:
- f1 — це функція, яка виконується, коли стан [promise] змінюється з [pending] на [resolved], тобто коли асинхронне завдання успішно виконало свою роботу. Вона отримує як параметр значення [result], передане командою [resolve(result)] асинхронного завдання;
- f2 — це функція, що виконується, коли стан [promise] змінюється з [pending] на [rejected], тобто коли асинхронне завдання не виконало свою роботу. Вона отримує як параметр значення [error], передане командою [reject(errror)] асинхронного завдання;
- f3 — це функція, що виконується після виконання методів [then] або [catch], тобто виконується завжди. Вона не отримує жодних параметрів;
Цей синтаксис повністю приховує події, на які здійснюється підписка. Проте це є підпискою, і, як і в попередньому прикладі, вона не виконує функції [f1, f2, f3] негайно. Ці функції будуть виконані або ні, коли відбудеться одна з подій [resolved, rejected], на яку здійснюється підписка.
Скрипт [async-04] ілюструє цей механізм:
'use strict';
// можна отримати результати (succes, failure) асинхронної функції
// без явного використання подій завдяки класу [Promise]
// цей клас неявним чином використовує події, але вони не відображаються в коді
// імпорти
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// початок
const débutScript = moment(Date.now());
console.log("[début du script],", heure(débutScript));
// визначення асинхронного завдання за допомогою обіцянки [Promise]
// асинхронне завдання є параметром конструктора [Promise]
const débutPromise1 = moment(Date.now());
const promise1 = new Promise(function (resolve) {
// журнал
console.log("[début fonction asynchrone de promise1],", heure(débutPromise1));
// асинхронний код
setTimeout(function () {
console.log("[fin fonction asynchrone de promise1],", heure(débutPromise1));
// асинхронне завдання повертає результат за допомогою функції [resolve]
// тоді обіцянка вважається виконаною
resolve('[réussite]');
}, 1000)
});
// результат обіцянки можна дізнатися за допомогою функції [promise1]
// коли обіцянка була виконана (resolve) або відхилена (reject)
// наступна інструкція — це підписка на подію [resolved] за допомогою методу [then]
// та на подію [rejected] за допомогою методу [catch]
// метод [finally] виконується як після then, так і після catch
promise1.then(result => {
// у разі успішного виконання обіцянки [evt resolved]
console.log(sprintf("[promise1.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
// у разі помилки [evt rejected]
console.log(sprintf("[promise1.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
// виконується в будь-якому випадку
console.log("[promise1.finally]", heure(débutPromise1));
});
// визначення асинхронного завдання за допомогою обіцянки [Promise]
const débutPromise2 = moment(Date.now());
const promise2 = new Promise(function (resolve, reject) {
// журнал
console.log("[début fonction asynchrone de promise2],", heure(débutPromise1));
// асинхронне завдання
setTimeout(function () {
console.log("[fin fonction asynchrone de promise2],", heure(débutPromise2));
// асинхронне завдання повертає результат за допомогою функції [reject]
// тоді обіцянка не виконана
reject('[échec]');
}, 2000)
});
// результат обіцянки можна дізнатися за допомогою функції [promise2]
// коли вона була вирішена (resolve) або відхилена (reject)
promise2.then(result => {
// у разі успішного виконання обіцянки [evt resolved]
console.log(sprintf("[promise2.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
// у разі помилки [evt rejected]
console.log(sprintf("[promise2.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
// виконується в будь-якому випадку
console.log(sprintf("[promise2.finally], %s", heure(débutPromise2)));
});
// відображатиметься перед повідомленнями асинхронних функцій та пов’язаних подій
console.log("[fin du code principal du script],", heure(débutScript));
// утиліта
function heure(début) {
// поточний час
const now = moment(Date.now());
// форматування часу
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);
// форматування тривалості
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// результат
return result;
}
Коментарі
- рядки 18–28: створення [Promise promise1]. Його асинхронна функція повертає результат через подію через секунду. Після запуску цієї асинхронної операції (запуск таймера) не чекають, поки вона поверне результат, а одразу переходять до коду в рядку 35;
- рядки 35–44: підписуємося на дві події [resolved, rejected], які може генерувати внутрішня асинхронна функція [promise1];
- рядки 46–71: повторюється та сама послідовність коду, що й раніше, для другого обіцянки [promise2];
- рядок 74: основний код скрипта завершено, але не весь скрипт, оскільки було запущено дві асинхронні дії. Ми повертаємося до циклу подій, де в якийсь момент відбудеться одна з подій [resolved, rejected] обіцянок [promise1, promise2]. Тоді вона буде оброблена;
- потім відбудеться повернення до циклу подій. І там буде оброблено друге подія [resolved, rejected] з обіцянок [promise1, promise2], коли воно відбудеться;
Виконання
[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
Коментарі
- рядок 3: запускається асинхронна функція [promise1], але не очікується її завершення, про яке повідомить подія;
- рядок 4: запускається асинхронна функція [promise2], але її завершення, про яке повідомить подія, не очікується;
- рядок 5: кінець основного коду та повернення до циклу подій;
- рядок 6: обробка події [fin fonction asynchrone de promise1]. Стан [promise1] зміниться на [resolved]. Про це повідомляє подія;
- рядок 7: оскільки [promise2] ще не завершило свою роботу, подія [promise1 resolved], яка щойно потрапила в цикл, буде оброблена методом [promise1.then], а потім методом [promise.finally] (рядок 8);
- рядки 9–11: той самий механізм відбувається, коли [promise2] переходить зі стану [pending] у стан [resolved];
11.5. скрипт [async-05]
Повернемося до коду конструктора об’єкта [Promise]:
У рядку 2 запускається асинхронне завдання [Promise]. Йому часто потрібно більше параметрів, ніж лише параметри [resolve, reject], які передає йому функція, що його інкапсулює. У цьому випадку створення [Promise] інкапсулюється у функцію, яка передасть їй параметри, необхідні для її асинхронної функції:
Наступний скрипт:
- визначає дві асинхронні функції, що повертають [Promise];
- запускає їх виконання паралельно та очікує, поки обидві функції завершаться, щоб виконати певну операцію;
'use strict';
// можна визначати асинхронні функції, які повертають тип [Promise]
// тоді їх можна позначити ключовим словом [async]
// імпортувати
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// початок
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// асинхронна функція, яка повертає обіцянку [Promise]
function async01(p1) {
return new Promise((resolve) => {
console.log("[début de la tâche asynchrone async01]");
// асинхронне завдання
const débutAsync01 = moment(Date.now());
setTimeout(function () {
console.log("[fin de la tâche asynchrone async01],", heure(débutAsync01));
// асинхронне завдання може повернути складний результат
resolve({
prop1: [10, 20, 30],
prop2: "abcd",
prop3: p1,
});
}, 1000)
});
}
// асинхронна функція, яка повертає обіцянку [Promise]
function async02(p1, p2) {
return new Promise(resolve => {
console.log("[début de la tâche asynchrone async02]");
// асинхронне завдання
const débutAsync02 = moment(Date.now());
setTimeout(function () {
console.log("[fin de la tâche asynchrone async02],", heure(débutAsync02));
// асинхронне завдання може повернути складний результат
resolve({
prop1: [11, 21, 31],
prop2: "xyzt",
prop3: p1 + p2
});
}, 2000)
})
}
// обидві асинхронні функції запускаються паралельно
// і чекаємо, поки вони обидві завершаться
// блока «then» виконується лише тоді, коли обидві функції випустили подію [resolved]
// блок catch виконується, щойно одна з двох функцій генерує подію [rejected]
Promise.all([async01(10), async02(10, 20)])
// результатом є масив [result1, result2], де [result1] — це результат, виданий [resolve] з [async01]
//, а [result2] — результат, виданий [resolve] з [async02]
.then(result => {
console.log(sprintf("[promise-all success], %s, result=%j", heure(débutScript), result));
})
// error — це результат, виданий першим [reject] однієї з двох асинхронних функцій
.catch(error => {
console.log(sprintf("[promise-all error], %s, erreur=%j", heure(débutScript), error));
})
// finally виконується після then або catch
.finally(() => {
console.log(sprintf("[promise-all finally], %s", heure(débutScript)));
});
// відобразиться перед повідомленнями асинхронних функцій та пов’язаних подій
console.log("[fin du code principal du script],", heure(débutScript));
// утиліта
function heure(début) {
// поточний час
const now = moment(Date.now());
// форматування часу
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);
// форматування тривалості
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// результат
return result;
}
Коментарі
- рядки 15–30: визначається функція [async01], яка повертає результат через 1 секунду за допомогою події таймера. Функція [async01], яка використовується в її результаті, знаходиться в рядку 26;
- рядки 33–47: аналогічно робимо з функцією [async02], яка повертає результат через 2 секунди за допомогою події таймера. Функція [async02] приймає два параметри, які використовуються в її результаті у рядку 44;
- коли будуть викликані обидві функції [async01, async02]:
- будуть запущені;
- повернуть коду, що їх викликав, дві обіцянки [promise1, promise2];
- після чого виконання повернеться до коду, що викликає, який продовжить свою роботу;
- приблизно через 1 секунду [async01] пошле подію, щоб повідомити, що вона завершила свою роботу. Ця подія буде поставлена в чергу в циклі подій, пов’язаному з результатом, переданим [async01] разом із подією;
- приблизно через 2 секунди той самий процес відбудеться для [async02];
- рядок 54: лише тепер виконуються асинхронні функції [async01, async02] (позначення async01(10) та async02(10,20)). Вони виконуються в масиві, переданому як параметр методу [Promise.all]. Відомо, що обидві функції [async01, async02] повертають обіцянку коду, що їх викликає. Отже, параметром функції [Promise.all] є масив із двох обіцянок;
- [Promise.all([promise1, promise2, …, promisen]).then(f1).catch(f2).finally(f3)] є підпискою на події:
- [Promise.all] має тип [Promise];
- функція [f1] методу [then] буде виконана, коли всі обіцянки [promise1, promise2, …, promisen] з масиву параметрів методу [all] перейдуть ізстану [pending] у стан [resolved]. Іншими словами, [f1] буде виконано, коли всі обіцянки з масиву успішно завершаться;
- функція [f2] методу [catch] буде виконана, щойно одна з обіцянок масиву перейде зі стану [pending] у стан [rejected]. Інакше кажучи, [f2] виконується, щойно один із промісів масиву завершиться невдало;
- функція [f3] методу [finally] буде виконана після виконання одного з методів [then, catch], отже, вона завжди виконується;
Виконання коду дає такі результати:
[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
- рядки 6–7: запускаються обидва асинхронні завдання [async01, async02]. Вони працюють паралельно. Паралельно виконується не їхній код, а їхні відповідні очікування на запитувані дані відбуваються одночасно;
- рядок 5: виконання основного коду скрипта завершено. Залишається дочекатися завершення двох асинхронних завдань [async01, async02];
- рядок 6: асинхронне завдання [async01] завершується приблизно через 1 с після запуску. Вона повертає результат за допомогою функції [resolve], тому її обіцянка в масиві на рядку 56 коду переходить зі стану [pending] у стан [resolved]. Цього недостатньо для виклику методу [then], рядки 59–60 коду;
- рядок 7: асинхронне завдання [async02] завершується приблизно через 2 с після запуску. Вона повертає результат за допомогою функції [resolve], тому її обіцянка в масиві у рядку 56 коду переходить зі стану [pending] у стан [resolved]. Метод [then] буде виконано, щойно це дозволить цикл обробки подій;
- рядок 8: виконується метод [then] з [Promise.all]. Вона отримує як параметр масив [result1, result2], де [result1] — це результат, виданий [async01], а [result2] — результатом, виданим функцією [async02];
- рядок 9: виконується метод [finally] функції [Promise.all];
11.6. скрипт [async-06]
Цей новий скрипт демонструє, як спільне використання ключових слів [async / await] дозволяє отримати асинхронний код, схожий на синхронний. Обробка подій повністю прихована, що полегшує розуміння коду.
Повернемося до попереднього прикладу, внесучи до нього такі зміни:
- додаємо третю асинхронну функцію [async03], яка повертає свій результат за допомогою методу [Promise.reject], що, таким чином, сигналізує циклу подій про те, що вона «не змогла» виконати свою роботу;
- послідовно виконуємо три асинхронні функції [async01, async02, async03]. У попередньому прикладі ми виконували паралельно асинхронні функції [async01, async02];
- до появи ключових слів [async/await] послідовне виконання асинхронних дій здійснювалося за допомогою вкладених одна в одну функцій [Promise]. Як тільки потрібно було виконати кілька таких асинхронних дій, кількість обіцянок відповідно зростала, а код ставав менш зрозумілим;
- за допомогою ключових слів [async/await] послідовне виконання асинхронних завдань здійснюється за синтаксисом, аналогічним до синтаксису виконання синхронних завдань:
// асинхронна функція — використання async / await
async function main() {
// послідовне виконання асинхронних завдань
try {
// виконання з очікуванням [async01]
const result1 = await async01(...);
console.log("[async01 result]=", result1);
// виконання з очікуванням [async02]
const result2 = await async02(...);
console.log("[async02 result]=", result2);
// виконання з очікуванням [async03]
const result3 = await async03(...);
console.log("[async03 result]=", result3);
} catch (error) {
// одна з асинхронних операцій завершилася невдало
console.log(sprintf("[sequential error]= %j, %s", error));
} finally {
// завершено
console.log("[fin exécution séquentielle des tâches asynchrones],");
}
- рядок 6: запускається асинхронна функція [async01] (ключове слово await), і очікується, поки вона опублікує свій результат за допомогою одного з методів [Promise.resolve, Promise.reject]. Отже, це блокуюча операція;
- рядок 6: ключове слово [await] перетворює асинхронну операцію [async01] на блокуючу. Відомо, що операція [async01] повертає результат двома способами:
- вона майже миттєво повертає коду, що її викликає, об’єкт [Promise];
- або пізніше публікує результат у циклі подій за допомогою методів [Promise.resolve, Promise.reject]. Саме цей останній результат отримує [result1] у рядку 6. Обробка подій дії [async01] стала невидимою;
- якщо результат [result] від [async01] публікується [Promise.resolve(result)], він присвоюється [result1] у рядку 6, і виконання продовжується у рядку 7;
- якщо результат функції [async01] публікується функцією [Promise.reject], це викликає виняток, і виконання коду переходить до рядка 14 — до блоку catch. Параметром клаузули [catch] є об’єкт помилки (error), опублікований [async01] із виразом [Promise.reject(error)]. Асинхронне завдання також може опублікувати помилку за допомогою [throw(error)]. Об’єкт [error] — це той, що отримано в [catch(error)];
- ключове слово [await] обов’язково має бути у функції, перед якою стоїть ключове слово [async] (рядок 2). Це ключове слово вказує, що функція [main] є асинхронною;
- у виразі [await f(…)] [f] має бути асинхронною функцією, яка повертає об’єкт [Promise] коду, що її викликає;
- те саме повторюємо для асинхронної дії [async02] у рядку 9 та [async03] у рядку 12;
Використовуючи ті самі ключові слова [async / await], можна забезпечити паралельне виконання асинхронних завдань за допомогою такого синтаксису:
try {
// паралельне виконання асинхронних завдань
const result = await Promise.all([async01(...), async02(...), async03(...)]);
console.log(sprintf("[parallel success], %s, result=%j", heure(débutParallel), result));
} catch (error) {
// одна з асинхронних операцій завершилася з помилкою
console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
} finally {
// завершено
console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
}
- рядок 3: маємо блокуючу операцію: очікуємо, поки три асинхронні завдання з масиву [async01(..), async02(..), async03(..)] опублікують свої результати у циклі подій за допомогою одного з методів [Promise.resolve, Promise.reject];
- якщо три асинхронні завдання опублікують свої результати за допомогою [Promise.resolve], то константа [result] є масивом [result1, result2, result3], де:
- [result1] — це результат, опублікований [async01] за допомогою виразу [Promise.resolve(result1)];
- [result2] — це результат, опублікований [async02] із виразом [Promise.resolve(result2)];
- [result3] — це результат, опублікований [async03] із виразом [Promise.resolve(result3)];
- якщо одне з трьох завдань опублікує свій результат із виразом [Promise.reject(error)], то виникне виняток;
- константа [result] у рядку 3 не отримує свого значення;
- виконання переходить безпосередньо до [catch] у рядку 5;
- параметр (error) блоку catch є об’єктом (error), опублікованим виразом [Promise.reject(error)];
Поєднуючи ці два синтаксиси, можна виконувати асинхронні завдання як послідовно, так і паралельно, причому все це за допомогою синтаксису, аналогічного синтаксису синхронного коду. Тому слід віддавати перевагу цьому синтаксису, який є набагато зрозумілішим за попередні. Цей синтаксис [async / await] доступний лише починаючи з версії 6 ECMAScript. Існує ще багато кодів JavaScript, що використовують обіцянки [Promise]. Тому важливо також розуміти, як вони працюють.
Повний код скрипта [async-06] виглядає так:
'use strict';
// паралельне або послідовне виконання декількох асинхронних завдань
// з ключовими словами async / await
// імпорт
import moment from 'moment';
import { sprintf } from 'sprintf-js';
// початок
const débutScript = moment(Date.now());
console.log("[début du code principal du script],", heure());
// асинхронна функція, що повертає [Promise]
function async01(débutAsync01) {
return new Promise(function (resolve) {
console.log("[début fonction asynchrone async01],", heure());
// асинхронна функція
setTimeout(function () {
console.log("[fin fonction asynchrone async01],", heure(débutAsync01));
// асинхронна операція може повернути складний результат
// тут — успіх
resolve({
prop1: [11, 21, 31],
prop2: "abcd"
});
}, 1000)
});
}
// асинхронна функція, що повертає [Promise]
function async02(débutAsync02) {
console.log("[début fonction asynchrone async02],", heure());
return new Promise(function (resolve) {
// асинхронна функція
setTimeout(function () {
console.log("[fin fonction asynchrone async02],", heure(débutAsync02));
// асинхронна операція може повернути складний результат
// тут — успіх
resolve({
prop1: [12, 22, 32],
prop2: "xyzt"
});
}, 2000)
})
}
// асинхронна функція, що повертає [Promise]
function async03(débutAsync03) {
console.log("[début fonction asynchrone async03],", heure());
return new Promise((resolve, reject) => {
// асинхронна функція
setTimeout(function () {
console.log("[fin fonction asynchrone async03],", heure(débutAsync03));
// асинхронна операція може повернути складний результат
// тут сталася помилка
reject({
prop1: [13, 23, 33],
prop2: "échec"
});
}, 3000)
})
}
// асинхронна функція — використання async / await
async function main() {
const débutSequential = moment(Date.now());
// послідовне виконання асинхронних завдань
console.log("------------ exécution séquentielle des tâches asynchrones lancée ------------------------")
try {
// виконання з очікуванням [async01]
const débutAsync01 = moment(Date.now());
const result1 = await async01(débutAsync01);
console.log("[async01 result]=", result1);
// виконання з очікуванням [async02]
const débutAsync02 = moment(Date.now());
console.log("début async02-------------", heure());
const result2 = await async02(débutAsync02);
console.log("[async02 result]=", result2);
// виконання з очікуванням [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) {
// одна з асинхронних операцій завершилася невдало
console.log(sprintf("[sequential error]= %j, %s", error, heure(débutSequential)));
} finally {
// завершено
console.log("[fin exécution séquentielle des tâches asynchrones],", heure(débutSequential));
}
const débutParallel = moment(Date.now());
// паралельне виконання асинхронних завдань
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) {
// одна з асинхронних операцій завершилася з помилкою
console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
} finally {
// завершено
console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
}
// завершено
console.log("[fin de la fonction main],", heure(débutSequential));
}
// виконання асинхронної функції main
main();
// з'явиться перед різними повідомленнями асинхронних функцій та їх подій
console.log("[fin du code principal du script],", heure(débutScript));
// утиліта
function heure(début) {
// поточний час
const now = moment(Date.now());
// форматування часу
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);
// форматування тривалості
result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
}
// результат
return result;
}
Результати виконання такі:
[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