Skip to main content

Що 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, синтаксичний цукор поверх промісів, а не заміна їм.

Швидкий приклад

javascript
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 року основний спосіб працювати з асинхронним кодом виглядав приблизно так:

javascript
getData(function (res) { process(res, function (result) { save(result, function (response) { console.log('Done!', response); }); }); });

Читати й налагоджувати такий код важко: помилки треба ловити вручну на кожному рівні, а логіка тоне у вкладеності. Саме цю проблему й називають callback hell.

Promise: основа, яку дав ES6

Проміс описує «обіцянку» повернути результат, коли операція завершиться, і має три стани: pending (очікує), fulfilled (виконано) і rejected (відхилено). Перехід зі стану pending відбувається рівно один раз і назад не повертається.

Обробка помилок стає передбачуваною:

javascript
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() можна пов'язати між собою: результат одного кроку передається в наступний.

javascript
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:

javascript
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()
ES2017async/await, синтаксичний цукор над промісами
ES2020 і пізнішеPromise.allSettled(), Promise.any(), Promise.withResolvers()

Отже, ES6 став фундаментом, а наступні версії лише покращили й спростили роботу з ним.

Чому це важливо:

До ES6Після ES6
Колбеки і callback hellПроміси та читабельна структура
Помилки ловляться вручну на кожному рівні.catch() для централізованої обробки
Багато вкладеностіЛанцюжки .then()
Немає гарантій порядку виконанняКерована, передбачувана асинхронність

Коротко: до ES6 асинхронність трималася на колбеках, ES6 дав проміси як керовану модель, а ES2017 додав async/await поверх них.

Типові помилки

  1. Не повертати проміс усередині .then(). Без return наступний крок отримає undefined і не дочекається операції:

    javascript
    fetch('/api/data') .then(res => { res.json(); }) // немає return .then(data => console.log(data)); // undefined
  2. Створювати проміс навколо того, що вже є промісом. new Promise(resolve => fetch(url).then(resolve)) це зайва обгортка, яка до того ж губить помилки; достатньо повернути сам fetch(url).

  3. Забувати .catch(). Необроблене відхилення дає unhandled promise rejection, а в Node.js може завершити процес.

  4. Вважати, що Promise.all запускає операції. Проміси стартують у момент створення, тому Promise.all лише чекає на них; послідовне створення в циклі з await виконає їх один за одним, а не паралельно.

  5. Брати Promise.all там, де потрібен Promise.allSettled. Перший падає на першій же помилці й губить успішні результати; коли треба дізнатися долю кожної операції, потрібен allSettled.

  6. Плутати проміс із синхронним значенням. .then() завжди виконується асинхронно, у черзі мікрозадач, тож код одразу після виклику відпрацює раніше за обробник.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.