Як працюють Promise та async/await в JavaScript?
Асинхронність - це ключовий аспект JavaScript, який дозволяє виконувати операції без блокування основного потоку виконання.
Еволюція асинхронності
1. Колбеки (Callbacks)
Перший спосіб роботи з асинхронним кодом:
function fetchData(callback) {
setTimeout(() => {
callback(null, 'Дані отримано');
}, 1000);
}
fetchData((error, data) => {
if (error) {
console.error(error);
} else {
console.log(data);
}
});
Проблема: Callback Hell при вкладених операціях:
fetchUser(userId, (error, user) => {
if (error) return handleError(error);
fetchPosts(user.id, (error, posts) => {
if (error) return handleError(error);
fetchComments(posts[0].id, (error, comments) => {
if (error) return handleError(error);
// Вкладеність стає неконтрольованою
});
});
});
2. Promise
Promise вирішує проблему callback hell:
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Дані отримано');
}, 1000);
});
}
fetchData()
.then(data => console.log(data))
.catch(error => console.error(error));
Створення Promise:
const myPromise = new Promise((resolve, reject) => {
const success = Math.random() > 0.5;
setTimeout(() => {
if (success) {
resolve('Операція успішна');
} else {
reject(new Error('Операція провалена'));
}
}, 1000);
});
Ланцюжки Promise:
fetchUser(userId)
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.then(comments => {
console.log('Всі дані отримано:', comments);
})
.catch(error => {
console.error('Помилка:', error);
});
Async/Await
Синтаксичний цукор над Promise, який робить асинхронний код схожим на синхронний:
Основний синтаксис
async function fetchUserData(userId) {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
return { user, posts, comments };
} catch (error) {
console.error('Помилка при отриманні даних:', error);
throw error;
}
}
Паралельне виконання
// Послідовно (повільно)
async function getDataSequential() {
const user = await fetchUser(1); // 1 секунда
const posts = await fetchPosts(1); // 1 секунда
// Загалом: 2 секунди
}
// Паралельно (швидко)
async function getDataParallel() {
const [user, posts] = await Promise.all([
fetchUser(1),
fetchPosts(1)
]);
// Загалом: 1 секунда
}
Методи Promise
Promise.all()
Чекає завершення всіх Promise:
const promises = [
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
];
Promise.all(promises)
.then(responses => {
// Всі запити завершені
return Promise.all(responses.map(r => r.json()));
})
.then(data => {
console.log('Всі дані:', data);
})
.catch(error => {
// Якщо хоча б один запит провалився
console.error('Помилка:', error);
});
Promise.allSettled()
Чекає завершення всіх Promise, незалежно від результату:
const promises = [
fetchUser(1),
fetchUser(999), // може провалитися
fetchUser(2)
];
Promise.allSettled(promises)
.then(results => {
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
console.log(`Promise ${index}: успіх`, result.value);
} else {
console.log(`Promise ${index}: помилка`, result.reason);
}
});
});
Promise.race()
Повертає результат першого завершеного Promise:
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 5000)
);
const dataFetch = fetch('/api/data');
Promise.race([dataFetch, timeout])
.then(response => response.json())
.catch(error => {
if (error.message === 'Timeout') {
console.log('Запит занадто довгий');
}
});
Поширені помилки
1. Забування await
// Неправильно
async function badExample() {
const data = fetchData(); // Повертає Promise, не дані
console.log(data); // Promise object
}
// Правильно
async function goodExample() {
const data = await fetchData();
console.log(data); // Фактичні дані
}
2. Не обробка помилок
// Неправильно
async function withoutErrorHandling() {
const data = await fetchData(); // Може кинути помилку
return data;
}
// Правильно
async function withErrorHandling() {
try {
const data = await fetchData();
return data;
} catch (error) {
console.error('Помилка:', error);
return null;
}
}
3. Послідовне виконання замість паралельного
// Повільно
async function sequential() {
const user1 = await fetchUser(1);
const user2 = await fetchUser(2);
return [user1, user2];
}
// Швидко
async function parallel() {
const [user1, user2] = await Promise.all([
fetchUser(1),
fetchUser(2)
]);
return [user1, user2];
}
Розуміння асинхронності критично важливе для ефективної роботи з JavaScript.