Коли слід використовувати Promises замість async/await? (Іноді розробники помиляються)

Нещодавно я спілкувався з другом, який також розробник, і, як завжди, ми знову потрапили в технічну дискусію. Ми часто обговорюємо такі питання, щоб залишатися у формі, допомагати один одному розв'язувати проблеми, з якими стикаємось в процесі розробки, і навіть інколи кидаємо один одному виклики.

Цього разу він запитав мене:

"Коли слід використовувати Promise замість async/await?"

Я вже стикався з цією плутаниною, тому відповідь здалася мені досить очевидною. Однак наступного дня в офісі я випадково поставив це питання колезі — і на моє здивування, він відповів неправильно.

Тоді я зрозумів:

Навіть досвідчені розробники можуть заплутатися між Promises і async/await, особливо під час інтерв'ю.

Тому я вирішив написати про це: чітко, практично і з реальними прикладами. Адже ця проста різниця може стати пасткою, якщо не бути уважним.

🚀 Спершу, у чому різниця?

Давайте почнемо з простого.

👉 Promise — це:

Об'єкт, який представляє асинхронну операцію, яка ще не завершена, але в кінцевому підсумку поверне результат або помилку.

👉 async/await — це:

Просто синтаксичний цукор, побудований на основі Promises.

💡 Простий приклад з реального життя

Уявімо, що ви отримуєте користувача, а потім — його пости за допомогою ID.

З використанням Promises:

getUser()
.then(user => {
return getPostsByUser(user.id);
})
.then(posts => {
console.log("Пости:", posts);
})
.catch(err => {
console.error("Помилка:", err);
});

З використанням async/await:

async function showUserAndPosts() {
try {
const user = await getUser();
const posts = await getPostsByUser(user.id);
console.log("Пости:", posts);
} catch (err) {
console.error("Помилка:", err);
}
}

🧠 То… що ж вибрати?

Ось що я дізнався з реального досвіду (не з підручників):

pic

❗ async/await не працює, як очікується, з Array.map() або forEach()

Це одна з найбільш поширених помилок, в яку я сам неодноразово потрапляв (і інколи ще роблю це).

Уявіть, що ви намагаєтесь використовувати await всередині Array.map():

const ids = [1, 2, 3];
const results = ids.map(id => {
const data = await fetchById(id); // ❌ Це не спрацює
return data;
});
console.log(results);

На перший погляд, здається, що це має працювати — адже ви використовуєте await в середині map, правда? Але це викличе помилку:

await можна використовувати тільки в асинхронних функціях або на верхньому рівні модулів

Навіть якщо зробити зворотний виклик async:

const results = ids.map(async id => {
return await fetchById(id);
});
console.log(results); // ❌ Все одно неправильно

Тепер це не викличе аварії, але results буде масивом незавершених Promises, а не реальними даними, які ви очікуєте.

✅ Правильний спосіб: використовуйте Promise.all() з async/await

Щоб отримати результати, потрібно зібрати всі Promises, а потім чекати, поки вони завершаться:

const ids = [1, 2, 3];
const promises = ids.map(id => fetchById(id));
const results = await Promise.all(promises);
console.log(results); // ✅ Тепер ви отримаєте виконані дані

Це правильний спосіб обробки асинхронних операцій всередині map, forEach або подібних методів.

🧵 Хочете послідовне виконання? Використовуйте for...of з await

Якщо ви хочете отримувати дані одне за одним у порядку, використовуйте простий цикл for...of:

const ids = [1, 2, 3];
const results = [];
for (const id of ids) {
const data = await fetchById(id);
results.push(data);
}

Це корисно, коли важливий порядок або коли ви не хочете завантажувати всі запити одночасно.

⚠️ async/await не працює на верхньому рівні за замовчуванням

Інша поширена помилка — спроба використати await поза асинхронною функцією:

const results = await fetchData(); // ❌ SyntaxError

Щоб виправити це, обгорніть у функцію async:

async function getData() {
const results = await fetchData();
console.log(results);
}

Або використовуйте await на верхньому рівні, який підтримується в сучасних ES модулях та інструментах, таких як Vite, Next.js або нові версії Node:

const results = await fetchData(); // ✅ Top-level await
console.log(results);

🎯 Відповідь для інтерв'ю

Ось що я відповідаю, коли мене запитують про це:

“Я віддаю перевагу async/await для робочих процесів з кількома етапами, оскільки це читається чисто і легко підтримується. Але коли використовую такі методи, як map або forEach, я переключаюсь на використання Promises з Promise.all() — тому що await не працює, як очікується, всередині них. А для утиліт або коли ланцюгування кращий, я залишаюсь на звичайних Promises.”

✍️ Підсумки

Це було не просто ще одне "технічне питання" — це виникло з реальних розмов з колегами та підготовки до інтерв'ю. Основний висновок?

  • І Promises, і async/await чудові — все залежить від контексту.
  • Будьте обережні з циклами і Array.map(), коли використовуєте await. Це не працюватиме, якщо ви не правильно обробляєте Promises.
  • Практикуйте перемикання між обома стилями.

Перекладено з: When Should You Use Promises Instead of async/await? (Sometimes Developers Get This Wrong)