Skip to content

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

Image

Асинхронна функція — це функція, виконання якої запускається, але результат якої не очікується. Коли виконання завершується, асинхронна функція генерує подію та передає свій результат через неї.

Цей режим роботи добре підходить для виконання у веб-браузері. Адже додаток, що виконується у браузері, є подієвим: додаток реагує на події, які здебільшого ініціює користувач (кліки, переміщення миші, введення тексту тощо). 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]:

Image

Скрипт [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]. Вони могли б мати іншу назву;

1
2
3
4
5
6
7
const promise=new Promise(function(resolve, reject){
     // запускається асинхронне завдання
    
     // у разі успіху: викликати resolve(result), де [result] — результат асинхронного завдання;
     // у разі невдачі: викликати reject(error), де [error] — об’єкт, що інкапсулює виниклу помилку;
}
// підписатися на події, що генеруються асинхронним завданням [Promise]
  • конструктор [Promise] виконує дві дії:
    • він створює подію для запуску виконання функції [function(resolve, reject)], яку йому передали як параметр, але не чекає на її результат і негайно повертає об’єкт [Promise] коду, що викликає. Цей об’єкт може мати чотири стани:
      • [pending]: асинхронна операція, яка викликала [Promise], ще не завершена;
      • [fulfilled]: асинхронна операція, яка повернула [Promise], успішно завершилася;
      • [rejected]: асинхронна операція, яка повернула [Promise], завершилася невдало;
      • [settled]: асинхронна операція, яка повернула [Promise], завершилася;

Коли конструктор повертає свій результат, створений об’єкт [Promise] перебуває у стані [pending], очікуючи на результати асинхронної функції;

  • (продовження)
    • асинхронне завдання з рядків 2–5 запускається негайно. Асинхронні завдання найчастіше є асинхронними завданнями вводу/виводу, які розкладаються наступним чином:
      1. виконання синхронного коду для запуску операції вводу-виводу з іншим пристроєм, наприклад, віддаленим сервером;
      2. очікування відповіді від цього пристрою;
      3. обробка цієї відповіді;

Саме фаза 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] як пов’язаними даними — зазвичай це об’єкт, що детально описує помилку, яка сталася;
      • отже, код, що викликає функцію, повинен підписатися на ці дві події, щоб отримувати сповіщення про доступність результату асинхронної функції;

Після завершення виконання асинхронного завдання, інкапсульованого в [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]:

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

const promise=new Promise(function(resolve, reject){
// запускається асинхронне завдання
// …
// у разі успіху: викликати resolve(result), де [result] — результат асинхронного завдання;
// у разі невдачі: викликати reject(error), де [error] — об’єкт, що інкапсулює помилку, яка виникла;
}
// підписатися на події, що генеруються асинхронним завданням

У рядку 2 запускається асинхронне завдання [Promise]. Йому часто потрібно більше параметрів, ніж лише параметри [resolve, reject], які передає йому функція, що його інкапсулює. У цьому випадку створення [Promise] інкапсулюється у функцію, яка передасть їй параметри, необхідні для її асинхронної функції:

'use strict';

// визначення асинхронної функції
function uneFonctionAsynchrone (p1, p2, , pn){
 return new Promise(function(resolve, reject){
     // запускається асинхронне завдання з параметрами (P1, p2, …, pn)
    // …
     // у разі успіху: викликати resolve(result), де [result] — результат асинхронного завдання;
     // у разі невдачі: викликати reject(error), де [error] — об’єкт, що інкапсулює помилку, яка виникла;
}
// підписатисяпідписуємося на події [resolved, rejected], які буде генерувати асинхронна функція [uneFonctionAsynchrone]

// через деякий час викликається асинхронна функція [uneFonctionAsynchrone]
uneFonctionAsynchrone(e1, e2, , en) ;

Наступний скрипт:

  • визначає дві асинхронні функції, що повертають [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