Що ES6 дав для асинхронності
Головне, що ES6 (2015) приніс для асинхронності, це Promise: об'єкт, який представляє результат асинхронної операції, що може бути отриманий зараз, пізніше або ніколи. Разом із ним з'явилися .then(), .catch(), ланцюжки викликів і статичні методи Promise.all та Promise.race, які прибрали з коду вкладені колбеки.
Теорія
TL;DR
- До ES6 асинхронність будували на колбеках, і глибока вкладеність давала callback hell.
- ES6 увів
Promise: «обіцянку» повернути результат, коли операція завершиться. .then()обробляє успіх,.catch()централізовано ловить помилки.- Ланцюжки
.then()роблять код лінійним: результат одного кроку йде в наступний. - ES6 також дав
Promise.resolve,Promise.reject,Promise.allіPromise.race. async/awaitце вже ES2017, синтаксичний цукор поверх промісів, а не заміна їм.
Швидкий приклад
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Дані отримано');
}, 1000);
});
promise.then(result => {
console.log(result); // Дані отримано
});Що тут відбувається:
new Promise()створює обіцянку.- Колбек отримує два аргументи:
resolve()позначає успішне завершення,reject()позначає помилку. - Метод
.then()викликається при успішному результаті. - Метод
.catch()ловить помилки.
До ES6: callback hell
До 2015 року основний спосіб працювати з асинхронним кодом виглядав приблизно так:
getData(function (res) {
process(res, function (result) {
save(result, function (response) {
console.log('Done!', response);
});
});
});Читати й налагоджувати такий код важко: помилки треба ловити вручну на кожному рівні, а логіка тоне у вкладеності. Саме цю проблему й називають callback hell.
Promise: основа, яку дав ES6
Проміс описує «обіцянку» повернути результат, коли операція завершиться, і має три стани: pending (очікує), fulfilled (виконано) і rejected (відхилено). Перехід зі стану pending відбувається рівно один раз і назад не повертається.
Обробка помилок стає передбачуваною:
const promise = new Promise((resolve, reject) => {
const success = false;
if (success) resolve('OK');
else reject('Помилка');
});
promise
.then(res => console.log('Успішно:', res))
.catch(err => console.log('Помилка:', err));Ланцюжки промісів
Кілька .then() можна пов'язати між собою: результат одного кроку передається в наступний.
fetch('/api/data')
.then(res => res.json())
.then(data => process(data))
.then(result => save(result))
.catch(error => console.error(error));Код став лінійним і читабельним, на відміну від вкладених колбеків. Один .catch() у кінці перехоплює помилку з будь-якого кроку ланцюжка.
Статичні методи Promise
| Метод | Що робить |
|---|---|
Promise.resolve(value) | Створює вже успішно завершений проміс |
Promise.reject(error) | Створює відхилений проміс |
Promise.all([p1, p2, ...]) | Запускає проміси паралельно і чекає завершення всіх |
Promise.race([p1, p2, ...]) | Повертає результат першого завершеного промісу |
Приклад Promise.all:
Promise.all([
fetch('/user'),
fetch('/posts'),
fetch('/comments')
])
.then(([userRes, postsRes, commentsRes]) => {
console.log('Усі дані отримано!');
})
.catch(err => console.error('Помилка:', err));Якщо хоча б один проміс буде відхилено, увесь
Promise.all()завершиться помилкою.
Що з'явилося пізніше
Питання саме про ES6, але важливо бачити всю еволюцію:
| Версія | Нове для асинхронності |
|---|---|
| ES6 (2015) | Promise, .then(), .catch(), Promise.all(), Promise.race() |
| ES2017 | async/await, синтаксичний цукор над промісами |
| ES2020 і пізніше | Promise.allSettled(), Promise.any(), Promise.withResolvers() |
Отже, ES6 став фундаментом, а наступні версії лише покращили й спростили роботу з ним.
Чому це важливо:
| До ES6 | Після ES6 |
|---|---|
| Колбеки і callback hell | Проміси та читабельна структура |
| Помилки ловляться вручну на кожному рівні | .catch() для централізованої обробки |
| Багато вкладеності | Ланцюжки .then() |
| Немає гарантій порядку виконання | Керована, передбачувана асинхронність |
Коротко: до ES6 асинхронність трималася на колбеках, ES6 дав проміси як керовану модель, а ES2017 додав async/await поверх них.
Типові помилки
-
Не повертати проміс усередині
.then(). Безreturnнаступний крок отримаєundefinedі не дочекається операції:javascriptfetch('/api/data') .then(res => { res.json(); }) // немає return .then(data => console.log(data)); // undefined -
Створювати проміс навколо того, що вже є промісом.
new Promise(resolve => fetch(url).then(resolve))це зайва обгортка, яка до того ж губить помилки; достатньо повернути самfetch(url). -
Забувати
.catch(). Необроблене відхилення даєunhandled promise rejection, а в Node.js може завершити процес. -
Вважати, що
Promise.allзапускає операції. Проміси стартують у момент створення, томуPromise.allлише чекає на них; послідовне створення в циклі зawaitвиконає їх один за одним, а не паралельно. -
Брати
Promise.allтам, де потрібенPromise.allSettled. Перший падає на першій же помилці й губить успішні результати; коли треба дізнатися долю кожної операції, потрібенallSettled. -
Плутати проміс із синхронним значенням.
.then()завжди виконується асинхронно, у черзі мікрозадач, тож код одразу після виклику відпрацює раніше за обробник.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.