За замовчуванням, JavaScript виконується в однопотоковому середовищі. Це означає, що операції обробляються послідовно: кожна наступна операція чекає на завершення попередньої. У випадку синхронного виконання, тривалі операції, такі як запити до мережі, обробка великих обсягів даних або взаємодія з файловою системою, можуть призвести до блокування основного потоку виконання. Це призводить до «зависання» інтерфейсу, роблячи застосунок нечутливим до дій користувача, що суттєво погіршує користувацький досвід.
Асинхронність розв'язує цю дилему, дозволяючи вашому коду запускати тривалі процеси, наприклад, завантаження даних, у фоновому режимі. При цьому основна частина програми, відповідальна за відгук інтерфейсу, продовжує працювати без перебоїв. Коли фонова операція завершиться, її результат буде інтегрований у застосунок, не спричиняючи жодних «завмирань» або затримок для користувача.
Уявіть це так: Ви робите онлайн-замовлення з кількох магазинів. Замість того, щоб чекати, поки кур'єр принесе товар з першого магазину, а потім ви підете замовляти в другому, і так далі — ви оформлюєте всі замовлення майже одночасно. Кожен магазин обробляє ваше замовлення у своєму темпі, і ви отримуєте повідомлення, коли щось готове до видачі чи доставки. Це дозволяє вам не гаяти час, просто чекаючи, і займатися своїми справами, поки інші виконують свою роботу — ось це асинхронний підхід!
Існує еволюційний шлях розвитку механізмів асинхронності в JavaScript, кожен з яких пропонує певні переваги та вирішує недоліки попередніх, роблячи «складне» все більш «простим».
Callback-функції були першим широко використовуваним підходом до асинхронності. Це функції, що передаються як аргументи в інші функції і виконуються після завершення певної асинхронної операції.
Простими словами: Ви замовили піцу з доставкою. Ви не стоїте біля дверей, чекаючи на кур'єра. Ви дали йому свій номер телефону і попросили зателефонувати, коли він буде біля вашого будинку. Цей дзвінок — це ваш колбек. Ви продовжуєте займатися своїми справами, поки чекаєте на дзвінок.
Найпростішими прикладами використання асинхронних колбеків є вбудовані функції setTimeout і setInterval:
JavaScript
setTimeout(callback, delay);
Тут функцію callback
буде викликано із затримкою мінімум delay мілісекунд.
Тепер давайте подивимося, як колбеки допомагають уникнути блокування основного потоку виконання.
JavaScript
console.log("Запускаємо таймер...");
setTimeout(
() => { console.log("Таймер спрацював!")},
2000
); // Затримка в 2000 мілісекунд (2 секунди)
console.log("Продовжуємо код...");
Результат виконання (порядок появи в консолі):
У цьому прикладі ми передаємо стрілкову функцію (() => { ... }
) як колбек до вбудованої функції setTimeout
. Ця стрілкова функція виконається лише після вказаної затримки, демонструючи, що асинхронний колбек – це просто функція, яку викликають пізніше. Тобто спочатку виконуються всі синхронні операції, а після цього - асинхронні.
Недолік: Головний недолік колбеків проявляється при вкладених асинхронних операціях. Якщо вам потрібно виконати одну асинхронну дію, а потім іншу, а потім ще одну, код перетворюється на «callback hell» або «пекельну піраміду».
JavaScript
getData(function(a) {
getMoreData(a, function(b) {
getEvenMoreData(b, function(c) {
getFinalData(c, function(d) {
console.log(d);
});
});
});
});
Такий код швидко стає некерованим і зовсім не «простим».
Історично, багато бібліотек та фреймворків активно використовували колбеки для своїх асинхронних операцій. Зокрема, у Node.js чимало вбудованих модулів (наприклад, для роботи з файловою системою або мережевими операціями) традиційно покладалися на колбек-функції. Аналогічно, у веб-розробці такі бібліотеки, як jQuery, широко використовували колбеки для AJAX-запитів, а також численні спеціалізовані бібліотеки для взаємодії з базами даних та реалізації анімацій раніше будувалися на колбек-підході.
Проміси є значним поліпшенням для роботи з асинхронними операціями. Проміс — це об'єкт, який представляє кінцеве завершення (або збій) асинхронної операції та її результуюче значення. Вони дозволяють упорядковувати асинхронні операції більш лінійно та передбачувано.
Простими словами: Ви замовили товар онлайн. Вам видали номер відстеження. Це не сам товар, але це об'єкт, який «обіцяє» (promise) вам, що товар або буде доставлений, або виникне проблема з доставкою. Ви можете перевіряти статус цього номера відстеження, не чекаючи фізично біля поштової скриньки. Це набагато зручніше, ніж постійно дзвонити кур'єру!
Стани промісу:
JavaScript
const URL = "https://jsonplaceholder.typicode.com/posts/1";
fetch(URL)
.then(response => response.json())
.then(data => console.log("2:Завантажені дані:", data))
.catch(error => console.error("2:Помилка:", error))
.finally(() => console.log("3:Завантаження завершене."));
console.log("1:Запит даних ініційовано за допомогою Промісів...");
Цей приклад ілюструє, як Проміси дають змогу ефективно керувати асинхронними мережевими запитами за допомогою fetch. Завдяки їм код залишається читабельним і лінійним, уникаючи «пекельної піраміди» колбеків.
fetch(URL)
, JavaScript одразу переходить до виконання наступних синхронних команд (як console.log("1:Запит даних ініційовано...")
), не чекаючи відповіді від сервера..then()
: Кожен .then()
-блок виконується послідовно, але асинхронно, лише після того, як попередній Проміс завершить свою роботу. Це дозволяє крок за кроком обробляти результат: спочатку отримати відповідь сервера, потім розібрати її як JSON. .catch()
централізовано перехоплює будь-які помилки, що виникли на попередніх етапах, роблячи код набагато чистішим. .finally()
завжди виконується після завершення всієї операції (успішно чи з помилкою), що зручно для фінальних дій, як-от приховування завантажувальних індикаторів.Порядок виведення в консоль:
fetch
запускає асинхронний процес і JavaScript одразу переходить до наступного синхронного рядка.finally
завжди виконується після повного завершення Промісу (успішно чи з помилкою).Таким чином, Проміси забезпечують надійний та зрозумілий механізм для організації складних асинхронних потоків.
Проміси забезпечують кращий контроль над потоком виконання та обробкою помилок за допомогою ланцюжків .then() та .catch()
, дозволяючи уникнути «callback hell» і зробити код значно «простішим».
Async/Await — це синтаксичний цукор над промісами, що дозволяє писати асинхронний код у синхронному стилі, роблячи його надзвичайно читабельним та легким для підтримки. Це найкращий спосіб перетворити «складне» на «просте» в асинхронності!
Простими словами: Уявіть, що ви доручили своєму помічнику (асинхронна функція) завантажити дані (проміс). Ви не чекаєте його, стоячи поруч. Ви просто кажете «Завантаж це, а я поки займусь іншим». Коли він повертається з даними, ви можете сказати «Добре, тепер очікуй (await
), поки я їх оброблю». Ви контролюєте потік, але не блокуєтеся. Це схоже на те, як ми мислимо в реальному житті, що робить цей підхід дуже інтуїтивним.
Ключові слова async/await
підходу:
async
перед оголошенням функції позначає її як асинхронну. Така функція завжди повертає проміс.await
може бути використане лише всередині async
функції (або на верхньому рівні модуля). Воно «призупиняє» виконання async
функції до тих пір, поки проміс, на який воно посилається, не буде виконаний (resolved) або відхилений (rejected). При цьому основний потік виконання не блокується.
JavaScript
async function getDataAsync(url) {
console.log("1:Запит даних ініційовано за допомогою async/await...");
try {
const response = await fetch(url);
const data = await response.json();
console.log("3:Успіх (async/await):", data);
} catch (error) {
console.error("3:Помилка (async/await):", error);
} finally {
console.log("4:Завантаження завершене (async/await).");
}
}
const URL = "https://jsonplaceholder.typicode.com/posts/1";
getDataAsync(URL);
console.log("2:Виконання програми триває (з async/await)...");
Цей приклад демонструє, як async/await
дозволяє писати асинхронний код, що виглядає і читається як синхронний, значно підвищуючи його зрозумілість та зручність у порівнянні з традиційними Промісами або колбеками.
getDataAsync
оголошена як async, що дозволяє використовувати ключове слово await
всередині неї. Коли JavaScript зустрічає await
, він «паузує» виконання цієї async
функції до тих пір, поки Проміс, на який посилається await
, не буде виконано. Це робить послідовність операцій дуже зрозумілою. try...catch:
async/await
чудово інтегрується зі стандартними блоками try...catch
для обробки помилок. Будь-яка помилка (наприклад, проблема з мережею або невірний URL), яка виникає під час виконання операції await
, буде перехоплена блоком catch
. Це забезпечує чистий і централізований механізм обробки винятків.finally
: Блок finally
виконується завжди, незалежно від того, чи завершилася асинхронна операція успіхом, чи помилкою. Це ідеальне місце для виконання завершальних дій, таких як приховування індикаторів завантаження або очищення ресурсів.await
«паузує» виконання конкретної async
функції, він не блокує основний потік виконання JavaScript. Це означає, що інші частини вашої програми продовжують працювати, поки асинхронна операція чекає на результат.Порядок виведення в консоль:
getDataAsync
.getDataAsync
ініціює асинхронний процес, і JavaScript одразу переходить до наступного синхронного рядка за межами цієї функції, не чекаючи її завершення.fetch
запит завершиться, і дані будуть успішно отримані та розпарсені (або виникне помилка).finally
виконується після повного завершення try
або catch
блоку.Зверніть увагу, як await fetch(url) та await response.json()
роблять код схожим на синхронний, але при цьому не блокують основний потік виконання. Це значно спрощує обробку послідовних асинхронних операцій та обробку помилок за допомогою стандартних блоків try...catch
. Це робить асинхронний код по-справжньому простим для розуміння!
Асинхронність є фундаментальною для всіх сучасних JavaScript-застосунків, що взаємодіють із зовнішніми ресурсами або виконують тривалі обчислення:
Як бачите, асинхронність — це не така вже й страшна тема, як може здатися на перший погляд. Завдяки колбекам, промісам та особливо async/await, ви можете писати код, який працює паралельно, забезпечуючи чудовий користувацький досвід. Хоча колбеки заклали основу, проміси та, особливо, async/await значно спростили розробку асинхронних застосунків, зробивши код більш читабельним та підтримуваним. Опанувавши ці інструменти, ви перетворите свій JavaScript-код зі «складного» на простий для керування та ефективний для виконання і зможете створювати високопродуктивні, чуйні та надійні JavaScript-застосунки, що є критично важливим для будь-якого сучасного розробника та команди.
А щоб краще зрозуміти, як саме JavaScript виконує асинхронний код "під капотом", радимо також прочитати розділ «Асинхронне програмування» в статті «Ключові характеристики та функціональність JavaScript» — вона допоможе вам глибше зануритись у механіку роботи асинхронності.
Розширте свої знання на нашому повному курсі навчання fullstack в ІТ компанії
GIT, HTML, CSS, JavaScript, TypeScript, Штучний інтелект, React, Redux, Linux, Node.js, PostgreSQL та MongoDB, Клієнт-серверна взаємодія, Docker, UNIT-тести, Спільна робота над проєктом, Індивідуальний проєкт.
ПерейтиРозширте свої знання на нашому повному курсі навчання проєктному менеджменту в ІТ компанії
Введення в ІТ, Технічна грамотність менеджера, Дослідження особливостей проєкту, Оцінка та планування задач, Контроль та реліз проєкту, Інструментарій розробника та штучний інтелект, Організація командної взаємодії, Закриття проєкту, Особливості продажів в ІТ.
Перейти