Async/await
Існує спеціальний синтаксис для роботи з промісами, який називається async/await. Він напрочуд простий для розуміння та використання.
Асинхронні функції
Почнемо з ключового слова async. Воно ставиться перед функцією, так:
Слово async має один простий зміст: ця функція завжди повертає проміс. Значення інших типів обертаються в успішний проміс автоматично.
Наприклад, ця функція поверне виконаний проміс з результатом 1 :
async function f() < return 1; >f().then(alert); // 1
Можна і явно повернути проміс, результат буде однаковим:
async function f() < return Promise.resolve(1); >f().then(alert); // 1
Отже, ключове слово async перед функцією гарантує, що ця функція в будь-якому випадку поверне проміс. Погодьтеся, досить просто? Але це ще не все. Є інше ключове слово - await, яке можна використовувати тільки всередині async-функцій.
Await
// працює тільки всередині async-функцій let value = await promise;
Ключове слово await змусить інтерпретатор JavaScript чекати доти, доки проміс праворуч від await не виконається. Після чого воно поверне його результат і виконання коду продовжиться.
У цьому прикладі проміс успішно виконається за 1 секунду:
async function f() < let promise = new Promise((resolve, reject) => < setTimeout(() =>resolve("готово!"), 1000) >); let result = await promise; // чекатиме, поки проміс не виконається (*) alert(result); // "Готово!" > f();
У цьому прикладі виконання функції зупиниться на рядку (*), доки проміс не виконається. Це відбудеться за секунду після запуску функції. Після чого змінну result буде записаний результат виконання промісу, і браузер відобразить alert-окно «готово!».
Зверніть увагу, хоча await і змушує JavaScript чекати на виконання промісу, це не забирає ресурсів процесора.Поки проміс не виконається, JS-рух може займатися іншими завданнями: виконувати інші скрипти, обробляти події і т.п.
По суті, це просто «синтаксичний цукор» для отримання результату промісу, наочніший, ніж promise.then.
Якщо ми спробуємо використовувати await всередині функції, оголошеної без async, отримаємо синтаксичну помилку:
Введення в асинхронний JavaScript
У цій статті ми коротко зупинимося на проблемах, пов'язаних із синхронним Javascript, а також ознайомимося з кількома асинхронними методами, які демонструють, як вони можуть допомогти нам подібні проблеми вирішити.
| Необхідна умова: | Базова комп'ютерна грамотність, достатнє розуміння основ JavaScript. |
|---|---|
| Ціль: | Ознайомитися з тим, що таке JavaScript, чим він відрізняється від синхронного і в яких випадках використовується. |
Синхронний JavaScript
Більшість функціональності, яку ми розглядали в попередніх навчальних модулях, є синхронною - ви запускаєте якийсь код, а результат повертається, як тільки браузер може його повернути. Давайте розглянемо простий приклад, як це працює:
const btn = document.querySelector("button"); btn.addEventListener("click", () => < alert("You clicked me!"); let pElem = document.createElement("p"); pElem.textContent = "This is a newly-added paragraph."; document.body.appendChild(pElem); >);
У цьому блоці коду команди виконуються одна за одною:
- Отримуємо посилання на елемент, який вже є у DOM.
- Додаємо до кнопки обробник події click так, що при натисканні на неї:
- Виводиться повідомлення alert().
- Після закриття повідомлення створюємо елемент (абзац).
- Потім додаємо до абзацу текст.
- Наприкінці додаємо створений абзац у тіло документа.
Поки виконується кожна операція, нічого більше не може статися - обробка документа призупинена. Так відбувається тому, що JavaScript є однопоточним.У кожний момент часу може виконуватися лише одна команда, що обробляється в єдиному головному потоці. Всі інші дії блокуються до завершення поточної команди.
Так і в прикладі вище: після натискання кнопки абзац не зможе з'явитися, поки не буде натиснуто кнопку OK у вікні повідомлення. Спробуйте самі:
Примітка: Важливо пам'ятати, що alert() , хоч і часто використовується для демонстрації синхронних операцій, що блокують, сильно не рекомендований до використання в реальних додатках.
Асинхронний JavaScript
З причин, згаданих раніше (наприклад, що стосуються блокування), безліч Web API особливостей тепер використовують асинхронний код, особливо ті, що мають доступ до зовнішніх пристроїв або отримують від них деякі ресурси, такі як отримання файлу з мережі, запит до бази даних та отримання даних із бази, доступ до потокового відео на веб-камері, перегляд дисплея на гарнітурі віртуальної реальності.
Чому важко працювати за допомогою синхронного коду? Погляньмо на невеликий приклад. Коли ви отримуєте зображення з сервера, ви не можете миттєво повернути результат. Це означає, що наступний (псевдо) код не спрацює:
let response = fetch("myImage.png"); let blob = response.blob(); // display your image blob in the UI somehowЦе відбувається тому, що ви не знаєте скільки часу займе завантаження картинки, отже, коли ви почнете виконувати другий рядок коду, згенерується помилка (можливо, періодично, можливо, щоразу), тому що response ще не доступний. Натомість, ваш код повинен дочекатися повернення response до того, як спробує виконати подальші інструкції.
Є два типи стилю асинхронного коду, з якими ви зіткнетеся в коді JavaScript, старий метод - колбеки (callbacks) і новий - проміси (promises). У наступних розділах ми познайомимося з кожним із них.
Асинхронні колбеки
Асинхронні колбеки — це функції, які визначаються як аргументи під час виклику функції, яка розпочне виконання коду на задньому фоні. Коли код на задньому фоні завершує свою роботу, він викликає колбек-функцію, що повідомляє, що робота зроблена, або повідомляє про труднощі в завершенні роботи. Зворотні виклики – трохи застаріла практика, але вони все ще використовуються в деяких старомодних, але часто використовуваних API.
Приклад асинхронного колбека другим параметром addEventListener() (як ми бачили вище):
btn.addEventListener("click", () => < alert("You clicked me!"); let pElem = document.createElement("p"); pElem.textContent = "This is a newly-added paragraph."; document.body.appendChild(pElem); >);Перший параметр — тип події, що обробляється, другий параметр — колбек-функція, що викликається при спрацюванні події.
При передачі колбек-функції як аргументу в іншу функцію, ми передаємо лише посилання на функцію як аргумент, отже колбек-функція не виконується миттєво. Вона викликається асинхронно усередині тіла, що містить функцію. Ця функція повинна виконувати колбек-функцію у потрібний момент.
Ви можете написати свою власну функцію, яка містить колбек-функцію. Давайте подивимося ще один приклад, у якому відбувається завантаження ресурсів через XMLHttpRequest API:
function loadAsset(url, type, callback) < const xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.responseType = type; xhr.onload = function() < callback(xhr.response); >; xhr.send(); > function displayImage(blob) < const objectURL = URL.createObjectURL(blob); const image = document.createElement("img"); image.src = objectURL; document.body.appendChild(image); >loadAsset("coffee.jpg", "blob", displayImage);Ми створили функцію displayImage() , яка представляє blob, переданий у ній, як об'єкт URL, і створює картинку, у якій відображається URL, додаючи їх у елемент документа . Однак, далі ми створюємо функцію loadAsset() , яка приймає колбек-функцію як параметр разом з URL для отримання даних і типом контенту. Для отримання даних з URL використовується XMLHttpRequest (часто скорочується до абревіатури "XHR") перед тим, як передати відповідь в колбек-функцію для подальшої обробки. У цьому випадку колбек-функція чекає, поки XHR закінчить завантаження даних (використовуючи обробник події onload ) перед відправкою даних в колбек-функцію.
Колбек-функції універсальні - вони не тільки дозволяють вам контролювати порядок, в якому запускаються функції та дані, що передаються між ними, вони також дозволяють передавати дані різним функціям залежно від обставин. Ви можете виконувати різні дії із завантаженою відповіддю, такі як processJSON() , displayText() та інші.
Зауважте, що не всі функції колбека асинхронні — деякі запускаються синхронно. Наприклад, при використанні Array.prototype.forEach() для перебору елементів масиву (запустіть приклад і подивіться вихідний код):
const gods = ["Apollo", "Artemis", "Ares", "Zeus"]; gods.forEach(function (eachName, index) < console.log(index + ". " + eachName); >);У цьому вся прикладі ми перебираємо масив з іменами грецьких богів і виводимо індекси і значення консоль. Очікуваний параметр forEach() — це Колбек-функція, яка містить два параметри: посилання на ім'я масиву та значення індексів. Однак ця функція не очікує жодних дій - вона запускається негайно.
Промисли
Проміси - новий стиль написання асинхронного коду, який використовується в сучасному Web API.Хорошим прикладом є fetch() API, який є сучаснішим та ефективнішим ніж XMLHttpRequest . Подивимося на короткий приклад, з нашої статті Fetching data from the server:
fetch("products.json") .then((response) => < if (!response.ok) < throw new Error(`HTTP error: $`); > return response.json(); >) .then( (json) => initialize(json)) .catch((err) => console.error(`Fetch problem: $`));Примітка: Ви можете переглянути закінчену версію на Github (дивіться вихідний код і запустіть приклад).
У прикладі видно, як fetch() приймає один параметр URL ресурсу, який потрібно отримати з мережі, і повертає проміс. Проміс - це об'єкт, що представляє асинхронну операцію, виконану вдало чи невдало. Він є як би проміжний стан. По суті, це спосіб браузера сказати: "я обіцяю повернутися до вас з відповіддю якнайшвидше", тому в дослівному перекладі "проміс" (promise) означає "обіцянку".
Може знадобитися багато часу, щоб звикнути до цієї концепції; це трохи нагадує Кота Шредінгера в дії. Жоден з можливих результатів ще не відбувся, тому операція fetch в даний час чекає на результат. Далі ми маємо три блоки коду наступних відразу після fetch() :
- Два then() блоки. Обидва включають функцію зворотного виклику, яка запуститься, якщо попередня операція закінчилася успішно, і кожна колбек-функція приймає на вхід результат попередньої успішно виконаної операції, таким чином ви можете виконувати операції послідовно. Кожен .then() блок повертає новий promise, це означає, що ви можете об'єднувати в ланцюжки блоки .then() , таким чином можна виконати кілька асинхронних операцій по порядку, одну за одною.
- catch() блок описується в кінці і буде запущений якщо якийсь .then() блок завершиться з помилкою - це аналогічно синхронному try.catch , помилка стає доступною всередині catch() , що може бути використане для повідомлення користувачу про тип помилки, що виникла. Однак синхронний try. catch не працюватиме з promise, хоча працюватиме з async/await, з якими ви познайомитеся пізніше.
Примітка: Ви дізнаєтеся набагато більше про promise пізніше в цьому модулі, так що не хвилюйтеся якщо ви щось не зрозуміли.
Черга подій
Асинхронні операції, такі як проміси, містяться в черга подій, яка запускається після завершення обробки основного потоку, щоб вони не блокували виконання JavaScript-коду. Поставлені в чергу операції завершаться якнайшвидше, а потім повернуть свої результати в середу JavaScript.
Проміси та колбек-функції
Проміси мають деяку подібність до старомодних колбек-функцій. По суті, вони є об'єктом, що повертається, до якого ви приєднуєте колбек-функції, замість того, щоб передавати колбеки в функцію.
Тим не менш, проміси зроблені спеціально для обробки асинхронних операцій, і мають багато переваг у порівнянні з колбеками:
- Ви можете об'єднати кілька асинхронних операцій разом, використовуючи кілька операцій .then() , передаючи результат одного в наступний як вхідні дані. Це набагато складніше зробити з колбеками, які часто закінчуються масивним «пеклом колбеків» (також відомим як callback hell).
- Зворотні виклики Promise завжди викликаються у строгому порядку, який вони поміщають у чергу подій.
- Обробка помилок набагато краща - всі помилки обробляються одним блоком .catch () в кінці блоку, а не обробляються індивідуально на кожному рівні «піраміди».
- Проміси уникають інверсії управління, на відміну колбеків, які втрачають повний контроль над тим, як буде виконуватися функція при передачі колбека в сторонню бібліотеку.
Природа асинхронного коду
Давайте розглянемо приклад, який додатково ілюструє природу асинхронного коду, показуючи, що може статися, коли ми повністю усвідомлюємо порядок виконання коду, і проблеми, пов'язані зі спробою трактувати асинхронний код як синхронний. Наступний приклад досить схожий на те, що ми бачили раніше. Одна з відмінностей полягає в тому, що ми включили ряд операторів console.log_static щоб проілюструвати порядок, в якому, як ви вважаєте, виконуватиметься код.
console.log("Starting"); let image; fetch("coffee.jpg") .then((response) => < console.log("It worked :)"); return response.blob(); >) .then((myBlob) => < const objectURL = URL.createObjectURL(myBlob); image = document.createElement("img"); image.src = objectURL; document.body.appendChild(image); >) . catch((error) => < console.log( "There has been a problem with your fetch operation: " + error.message, ); console.log("All done!");Браузер почне виконувати код, побачить перший консольний оператор (Starting) і виконає його, а потім створить змінну image .
Потім він переміститься на наступний рядок і почне виконувати блок fetch () , але оскільки fetch () виконується асинхронно без блокування, виконання коду триває після коду, пов'язаного з промісом, тим самим досягаючи остаточного оператора ( All done! ) і виводячи його на консоль .
Тільки після того, як блок fetch() повністю завершить роботу і доставить свій результат через блоки .then(), ми нарешті побачимо друге повідомлення console.log() (It worked;)). Таким чином, повідомлення з'явилися не в тому порядку, на який ви могли очікувати:
Якщо ви заплуталися, розглянемо такий невеликий приклад:
console.log("registering click handler"); button.addEventListener("click", () => < console.log("get click"); >); console.log("all done");Цей приклад дуже схожий на попередній у своїй поведінці — перше і третє повідомлення console.log () будуть показані негайно, але друге буде заблоковано, поки хтось не натисне кнопку миші. Попередній приклад працює аналогічно, за винятком того, що в цьому випадку друге повідомлення блокується ланцюжком промісів, отримуючи ресурс, а потім відображаючи його на екрані, а не клацанням миші.
У менш простому прикладі коду така система може викликати проблему - ви не можете включити блок асинхронного коду, який повертає результат, на який ви потім покладатиметеся в блоці синхронного коду. Ви просто не можете гарантувати, що асинхронна функція повернеться до того, як браузер обробить синхронний блок.
Щоб побачити це в дії, спробуйте змінити третій виклик console.log() наступним чином:
console.log("All done!" + image + "displayed.");Тепер замість третього повідомлення має виникнути така помилка:
TypeError: image is undefined; can't access its "src" property
Це тому, що в той же час браузер намагається запустити третій console.log() , блок fetch() ще не закінчив виконання, тому змінна image ще не має значення.
Примітка: З міркувань безпеки ви не можете застосовувати fetch() до файлів вашої локальної системи (або запустити інші такі операції локально); щоб запустити локально приклад вище, вам необхідно запустити його через локальний веб-сервер.
Активне навчання: зробіть усе це асинхронно!
Щоб виправити проблемний приклад з fetch() і змусити всі три повідомлення console.log() з'явитися в бажаному порядку, можна також запустити третє повідомлення console.log() асинхронно. Цього можна досягти, перемістивши його всередину іншого блоку .then() приєднаного до кінця другого або просто перемістивши його всередину другого блоку then() . Спробуйте виправити це.
Висновок
У своїй основній формі JavaScript є синхронною, блокуючою, однопотоковою мовою, в якій одночасно може виконуватися лише одна операція. Але веб-браузери визначають функції та API, які дозволяють нам реєструвати функції, які не повинні виконуватися синхронно, а повинні викликатися асинхронно, коли відбувається будь-яка подія (час, взаємодія користувача з мишею або отримання даних через мережу, наприклад). Це означає, що ви можете дозволити коду робити кілька речей одночасно, не зупиняючи і не блокуючи основний потік.
Чи будемо запускати код синхронно чи асинхронно, буде залежати від того, що ми намагаємося зробити.
Є моменти, коли ми хочемо, щоб все завантажувалося і відбувалося зараз. Наприклад, при застосуванні деяких стилів користувача до веб-сторінки ви хочете, щоб стилі застосовувалися якнайшвидше.
Якщо ми виконуємо операцію, яка вимагає часу, наприклад, запит до бази даних та використання отриманих результатів для заповнення шаблонів, краще виштовхнути це з основного потоку та виконати завдання асинхронно. Згодом ви дізнаєтеся, коли має сенс вибирати асинхронну техніку замість синхронної.
Асинхронне програмування з async/await
Порівняно новими доповненнями JavaScript є асинхронні функції та ключове слово await. Ці можливості в основному є синтаксичним цукром над обіцянками (промісами), полегшуючи написання та читання асинхронного коду. Вони роблять асинхронний код подібним до синхронного. Ця стаття допоможе вам розібратися, що до чого.
Умови: базова комп'ютерна грамотність, знання основ JS, розуміння азів асинхронного коду та обіцянок.
Ціль: зрозуміти, як влаштовані обіцянки, і як вони використовуються.
Основи async/await
Використання async/await складається із двох частин.
Ключове слово async
Перш за все, у нас є ключове слово async, яке ми розміщуємо перед оголошенням функції, щоб зробити її асинхронною.Асинхронна функція – це функція, яка передбачає можливість використання ключового слова await для запуску асинхронного коду.
Спробуйте набрати в консолі браузера наступне:
function hello() < return 'Hello' >hello()Функція поверне Hello. Нічого незвичайно, правда?
Але що якщо ми перетворимо її на асинхронну функцію? Спробуйте зробити таке:
async function hello() < return 'Hello' >hello()Тепер виклик функції повертає обіцянку. Це одна з особливостей асинхронних функцій – вони повертають значення, які гарантовано перетворюються на обіцянки.
Ви також можете створити асинхронний функціональний вираз, наприклад:
let hello = async function() < return hello() >hello()Також можна використовувати стрілочні функції:
Всі ці функції роблять те саме.
Щоб отримати значення завершеної обіцянки, ми можемо використовувати блок .then():
hello().then((value) => console.log(value))Таким чином, додавання ключового слова Async змушує функцію повертати обіцянку замість значення. Крім того, це дозволяє синхронним функціям уникати будь-яких накладних витрат, пов'язаних із запуском та підтримкою використання await. Просте додавання async перед функцією забезпечує автоматичну оптимізацію коду двигуном JS. Круто!
Ключове слово await
Переваги асинхронних функцій стають ще очевиднішими, коли ви комбінуєте їх із ключовим словом await. Вона може бути додана перед будь-якою функцією, що базується на обіцянках, щоб змусити її чекати завершення обіцянки, а потім повернути результат. Після цього виконується наступний блок коду.
Ви можете використовувати await під час виклику будь-якої функції, яка повертає обіцянку, включаючи функції Web API.
Ось тривіальний приклад:
async function hello() < return greeting = await Promise.resolve('Hello') >hello().then(alert)Зрозуміло, наведений код марний, він лише служить демонстрацією синтаксису. Давайте рухатись далі і подивимося на реальний приклад.
Переписуємо код на обіцянках з використанням async/await
Візьмемо приклад із fetch з попередньої статті:
fetch('coffee.jpg') .then(response => response.blob()) .then(myBlob => < let objectURL = URL.createObjectURL(myBlob) let image = document.createElement('img') image.src = objectURL document.body.appendChild(image) >).У вас вже має бути розуміння того, що таке обіцянки і як вони працюють, але давайте перепишемо цей код з використанням async/await, щоб побачити наскільки все стало простіше:
async function myFetch() < let response = await fetch('coffee.jpg') let myBlob = await response.blob() let objectURL = URL.createObjectURL(myBlob) let image = document.createElement('img') image.src = objectURL document.body.appendChild (image) > myFetch ().Це робить код набагато простішим і легшим для розуміння – жодних блоків .then()!
Використання ключового слова async перетворює функцію на обіцянку, тому ми можемо використовувати змішаний підхід з обіцянок і await, виділивши другу частину функції в окремий блок з метою підвищення гнучкості:
async function myFetch() < let response = await fetch('coffee.jpg') return await response.blob() >myFetch().then((blob) => < let objectURL = URL.createObjectURL(blob) let image = document.createElement('image') image.src = objectURL document.body.appendChild(image) >).catch(e => console.log(e))Ви можете переписати приклад або запустити живе демо (див. також вихідний код).
Але як це працює?
Ми обернули код всередині функції та додали ключове слово async перед ключовим словом function. Вам потрібно створити асинхронну функцію, щоб визначити блок коду, в якому запускатиметься асинхронний код; await працює лише всередині асинхронних функцій.
Ще раз: await працює лише в асинхронних функціях.
Всередині функції myFetch() код дуже нагадує версію на обіцянках, але з деякими відмінностями. Замість використання блоку .then() після кожного методу, заснованого на обіцянках, достатньо додати ключове слово await перед викликом методу та присвоїти значення змінної. Ключове слово await змушує двигун JS призупинити виконання коду на даному рядку, дозволяючи виконуватися іншому коду, доки асинхронна функція не поверне результат. Як тільки вона виконається, код продовжить виконання наступного рядка.
Наприклад:let response = await fetch('coffee.jpg')Значення, що повертається обіцянкою fetch(), присвоюється змінній response, коли це значення стає доступним, і «парсер» зупиняється на цій лінії до завершення обіцянки. Як тільки значення стає доступним, парсер переходить до наступного рядка коду, який створює Blob. Цей рядок також викликає заснований на обіцянках асинхронний метод, тому ми також використовуємо await. Коли результат операції повертається, ми повертаємо її з функції myFetch().
Це означає, що коли ми викликаємо функцію myFetch(), вона повертає обіцянку, тому ми можемо додати до неї .then(), всередині якої обробляємо відображення зображення на екрані.
Ви, напевно, думаєте "От здорово!", і ви маєте рацію - менше блоків .then() для обертання коду, все це виглядає як синхронний код, тому він інтуїтивно зрозумілий.
Додаємо обробку помилок
Якщо ви бажаєте додати обробку помилок, у вас є кілька варіантів.
Ви можете використати синхронну структуру try. catch разом із async/await. Цей приклад є розширеною версією наведеного вище коду:
async function myFetch() < try< let response = await fetch('coffee.jpg') let myBlob = await response.blob() let objectURL = URL.createObjectURL(myBlob) let image = document.createElement('img') image .src = objectURL document.body.appendChild(image) >catch(e) < console.log(e) >> myFetch()Блок catch()<> приймає об'єкт помилки, який ми назвали "e"; тепер ми можемо вивести його в консоль, це дозволить нам отримати повідомлення про те, де коду сталася помилка.
Якщо ви хочете використовувати другу версію коду, показаного вище, вам слід просто продовжувати використовувати гібридний підхід і додати блок .catch() в кінець виклику .then(), таким чином:
async function myFetch() < let response = await fecth('coffee.jpg') return await response.blob() >myFetch().then((blob) => < let objectURL = URL.createObjectURL let image = document.createElement ('img') image.src = objectURL document.body.appendChild(image) >).catch(e => console.log(e))Це можливо, оскільки блок .catch() перехоплюватиме помилки, що виникають як в асинхронній функції, так і в ланцюжку обіцянок. Якщо ви використовуєте блок try/catch, то не зможете обробляти помилки, що виникають при виклику функції myFetch().
Очікування Promise.all()
Async/await заснований на обіцянках, тому ви можете використовувати всі можливості останніх. До них, зокрема, відноситься Promise.all() — ви легко можете додати await до Promise.all(), щоб записати всі значення, що повертаються способом, схожим на синхронний код. Знову візьмемо приклад із попередньої статті. Тримайте вкладку з ним відкритою, щоб порівняти з наведеним нижче кодом.
З async/await (див. живе демо та вихідний код) він виглядає так:
async function fetchAndDecode(url, type) < let repsonse = await fetch(url) let content if(type === 'blob')< content = await response.blob() >else if(type === 'text') < content = await response.text() >return content > async function displayContent() < let coffee = fetchAndDecode('coffee.jpg', 'blob') let tea = fetchAndDecode('tea.jpg', 'blob') let description = fetchAndDecode('description.txt', 'text') let values = await Promise.all([coffee, tea, description]) let objectURL1 = URL.createObjectURL(values[0]) let objectURL2 = URL.createObjectURL(values [1]) let descText = values[2] let image1 = document.createElement('img') let image2 = document.createElement('img') image1.src = objectURL1 image2.src = objectURL2 document.body.appendChild(image1) document.body.appendChild(image2) let para = document.createElement('p') para.textContent = descText document.body.appendChild(para) >displayContent() .catch(e => console.log(e))Ми легко перетворили функцію fetchAndDecode() на асинхронну за допомогою парочки змін. Зверніть увагу на рядок:
let values = await Promise.all([coffee, tea, description])Використовуючи await, ми отримуємо результати трьох обіцянок у змінній values, способом, схожим на синхронний код. Ми повинні обернути всю функцію на нову асинхронну функцію, displayContent(). Ми не досягли сильного скорочення коду, але отримали можливість витягти більшу частину коду з блоку .then(), що забезпечує корисне спрощення і робить код більш читаним.
Для обробки помилок ми додали блок .catch() у наш виклик displayContent(); він опрацьовує помилки обох функцій.
Запам'ятайте: також можна використовувати блок .finally() для отримання звіту про виконання операції - ви можете побачити це в дії в живому демо (див. також вихідний код).
Недоліки async/await
Async/await має кілька недоліків.
Async/await робить код схожим на синхронний і в певному сенсі змушує його поводитись більш синхронно.Ключове слово await блокує виконання наступного коду до завершення обіцянки, як це відбувається в синхронній операції. Це дозволяє виконувати інші завдання, але ваш власний код є заблокованим.
Це означає, що ваш код може бути уповільнений великою кількістю очікуваних обіцянок, що йдуть один за одним. Кожен await буде чекати завершення попереднього, в той час як ми хотіли б, щоб обіцянки почали виконувати одночасно, так ніби ми не використовуємо async/await.
Існує шаблон проектування, що дозволяє пом'якшити цю проблему — відключення всіх процесів обіцянок шляхом збереження об'єктів Promise у змінних та подальшого очікування. Погляньмо на те, як це реалізується.
У нашому розпорядженні є два приклади – slow-async-await.html (див. вихідний код) та fast-async-await.html (див. вихідний код). Обидва приклади починаються з функції-обіцянки, яка імітує асинхронну операцію за допомогою setTimeout():
function timeoutPromise(interval) < return new Promise((resolve, reject) =>< setTimeout(function()< resolve('done') >, interval) >) >Потім слідує асинхронна функція timeTest(), яка очікує трьох викликів timeoutPromise():
async function timeTest()
Кожен із трьох викликів timeTest() завершується записом часу виконання обіцянки, потім записується час виконання всієї операції:
let startTime = Date.now() timeTest().then(() => < let finishTime = Date.now() let timeTaken = finishTime - startTime alert('Time taken in milliseconds: ' + timeTaken) >)У кожному випадку функція timeTest() відрізняється.
У slow-async-await.html timeTest() виглядає так:
async function timeTest()
Тут ми просто очікуємо три виклики timeoutPromise, щоразу встановлюючи затримку в 3 секунди. Кожен виклик чекає на завершення попереднього — якщо ви запустите перший приклад, то побачите модальне вікно приблизно через 9 секунд.
У fast-async-await.html timeTest() виглядає так:
async function timeTest()
Тут ми зберігаємо три об'єкти Promise у змінних, що змушує пов'язані з ним процеси виконуватися одночасно.
Далі ми очікуємо на їхні результати — оскільки обіцянки починають виконуватися одночасно, обіцянки завершаться також одночасно; коли ви запустите другий приклад, побачите модальне вікно приблизно через 3 секунди!
Вам слід обережно тестувати код та пам'ятати про це при зниженні продуктивності.
Ще однією незначною незручністю є необхідність обертання очікуваних обіцянок в асинхронну функцію.
Використання async/await спільно з класами
На завершення зазначимо, що ви можете додавати async навіть у методах створення класів, щоб вони повертали обіцянки, і чекати на обіцянки всередині них. Візьмемо код зі статті про об'єктно-орієнтований JS і порівняємо його з модифікованою за допомогою async версією:
class Person < constructor(first, last, age, gender, interests)< this.name = < first, last >this.age = age this.gender = gender this.interests = interests > async greeting()< return await Promise. resolve(`Hi! I'm $`) > farewell() < console.log(`$has left the building. Bye for now!`) > > let han = new Person('Han', 'Solo', 25 , 'male', ['Smuggling'])Метод класу може бути використаний так:
han.greeting().then(console.log)Підтримка браузерів
Одним із перешкод використання async/await є відсутність підтримки старих браузерів. Ця можливість доступна майже у всіх сучасних браузерах, також як обіцянки; деякі проблеми існують в Internet Explorer та Opera Mini.
Якщо ви хочете використовувати async/await, але потребуєте підтримки старих браузерів, можете використовувати бібліотеку BabelJS — вона дозволяє використовувати новітній JS, перетворюючи його на відповідний для конкретного браузера.
Висновок
Async/await дозволяє писати асинхронний код, який легко читати та підтримувати.Незважаючи на те, що async/await підтримується гірше за інші способи написання асинхронного коду, його безумовно варто вивчити.
