Асинхронність у JavaScript: проміси, async/await і патерни
03.04.2026Чому асинхронність важлива
У сучасних вебдодатках асинхронні операції — запити до сервера, читання файлів, таймери та обробка подій — становлять основу взаємодії з користувачем. Розуміння того, як JavaScript обробляє асинхронність, допомагає писати більш передбачуваний, швидкий і надійний код.
Базові поняття: цикл подій, стек і черги
JavaScript використовує однотредовий модель виконання з циклом подій. Основні компоненти цієї моделі:
- Стек викликів (call stack): виконує синхронні функції.
- Web APIs: браузерні або середовищні сервіси, що виконують асинхронну роботу (наприклад, fetch, setTimeout).
- Черги задач (macrotasks) і мікротасків (microtasks): місця, куди ставляться колбеки для подальшого виконання.
Розрізнення мікротасків і макротасків впливає на порядок виконання обробників і може призвести до неочевидної поведінки, якщо його не враховувати.
Проміси: основи
Проміс — це об’єкт, який представляє результат асинхронної операції. Його стан може бути pending, fulfilled або rejected. Основні методи: then, catch і finally.
// Приклад промісу
fetch('/api/data')
.then(response => response.json())
.then(data => console.log('Дані:', data))
.catch(err => console.error('Помилка:', err))
Async/await: синтаксичний цукор над промісами
Async/await робить асинхронний код схожим на синхронний, спрощуючи читання і обробку помилок.
async function loadData() {
try {
const response = await fetch('/api/data')
const data = await response.json()
console.log('Дані:', data)
} catch (err) {
console.error('Помилка:', err)
}
}
Мікротаски vs макротаски
Проміси ставлять виконання в чергу мікротасків, яка обробляється перед наступною макрозадачею. Це означає, що проміс може виконатися перед setTimeout навіть якщо setTimeout має нульову затримку.
console.log('start')
setTimeout(() => console.log('timeout'), 0)
Promise.resolve().then(() => console.log('promise'))
console.log('end')
// Очікуваний порядок: start, end, promise, timeout
Патерни асинхронності
Ось набір корисних підходів для роботи з асинхронними операціями:
- Паралельне виконання з Promise.all — підходить для незалежних операцій, коли потрібні всі результати.
- Promise.allSettled — коли потрібно отримати результати незалежно від помилок.
- Promise.race — коли потрібен перший результат або таймаут.
- Послідовне виконання — використовувати цикл з await для крокової обробки з контролем порядку.
- Обмежена паралельність (concurrency limit) — важливий патерн для запитів до API, щоб не перевантажувати сервер.
- AbortController для скасування запитів — дозволяє відміняти fetch та інші операції.
Приклад обмеження паралельності
async function mapWithLimit(items, limit, handler) {
const results = []
const executing = []
for (const item of items) {
const p = Promise.resolve().then(() => handler(item))
results.push(p)
executing.push(p)
if (executing.length >= limit) {
await Promise.race(executing)
// очищаємо виконані проміси
executing.splice(executing.findIndex(e => e.done), 1)
}
}
return Promise.all(results)
}
Практичні поради
- Завжди обробляйте помилки: використовувати catch або try/catch в async-функціях.
- Не блокувати основний потік важкими синхронними операціями; переміщуйте їх в воркери при потребі.
- Враховуйте порядок мікро- і макротасків при налагодженні несподіваної поведінки.
- Для скасування довгих операцій застосовуйте AbortController або власні таймаути.
- Документуйте очікувану модель виконання в коді, щоб колеги розуміли обмеження та припущення.
Висновок
Асинхронність в JavaScript — не просто механіка викликів, а набір концепцій, які визначають, як ваш код реагує на події й ресурси. Розуміння промісів, async/await, мікро- та макротасків, а також патернів обробки дозволить писати більш передбачуваний і продуктивний код. Починайте з базових прикладів, поступово вводьте патерни обмеження паралельності й скасування, і завжди тестуйте поведінку при різних затримках мережі та навантаженні.