Пошук уроків, статей та іншого контенту
Використаємо Promise.all, allSettled, race та any для координації групи асинхронних операцій.
Коли в програмі є кілька асинхронних операцій, їх можна запускати паралельно та координувати результат за допомогою статичних методів Promise:
Promise.all() — дочекатися успішного виконання всіх операцій;
Promise.allSettled() — дочекатися завершення всіх операцій незалежно від результату;
Promise.race() — отримати результат першої завершеної операції;
Promise.any() — отримати результат першої успішної операції.
Ці методи називають Promise combinators — комбінаторами промісів.
Вони приймають ітерований об’єкт, зазвичай масив промісів, і повертають новий проміс.
Promise.all()Promise.all() використовується, коли потрібно успішно виконати всі асинхронні операції.
const results = await Promise.all([
loadUser(),
loadPosts(),
loadNotifications()
]);Новий проміс:
виконується, якщо виконалися всі передані проміси;
отримує масив результатів у тому самому порядку, у якому проміси були передані;
відхиляється одразу після першої помилки.
Операції виконуються паралельно, але порядок результатів не залежить від того, яка операція завершилася першою.
const delay = (value, milliseconds) =>
new Promise((resolve) => {
setTimeout(() => resolve(value), milliseconds);
});
const results = await Promise.all([
delay("first", 300),
delay("second", 100),
delay("third", 200)
]);
console.log(results);
// ["first", "second", "third"]Хоча "second" завершився першим, результат залишається на другій позиції.
Promise.all()Якщо хоча б один проміс відхиляється, Promise.all() відхиляється:
const delay = (value, milliseconds, shouldFail = false) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldFail) {
reject(new Error(`Не вдалося отримати: ${value}`));
return;
}
resolve(value);
}, milliseconds);
});
try {
const results = await Promise.all([
delay("user", 100),
delay("posts", 200, true),
delay("notifications", 300)
]);
console.log(results);
} catch (error) {
console.error(error.message);
}У цьому випадку catch спрацює після помилки під час завантаження "posts".
Важливо: відхилення результуючого промісу не скасовує інші операції. Вони можуть продовжити виконання у фоновому режимі.
Promise.all()Використовуйте Promise.all(), якщо:
усі результати потрібні для подальшої роботи;
помилка будь-якої операції повинна зупинити обробку;
операції не залежать одна від одної і їх можна запускати паралельно.
Promise.allSettled()Promise.allSettled() чекає завершення всіх промісів — як успішних, так і відхилених.
Результатом є масив об’єктів із полем status:
{ status: "fulfilled", value: ... };
{ status: "rejected", reason: ... }.
const results = await Promise.allSettled([
Promise.resolve("успішна операція"),
Promise.reject(new Error("помилка операції"))
]);
console.log(results);
// [
// { status: "fulfilled", value: "успішна операція" },
// {
// status: "rejected",
// reason: Error: помилка операції
// }
// ]Promise.allSettled() сам не відхиляється через помилки окремих промісів. Тому зазвичай результати перевіряють вручну:
const results = await Promise.allSettled([
fetchUser(),
fetchPosts(),
fetchRecommendations()
]);
const successfulResults = results
.filter((result) => result.status === "fulfilled")
.map((result) => result.value);
const failedResults = results
.filter((result) => result.status === "rejected")
.map((result) => result.reason);
console.log("Успішні результати:", successfulResults);
console.log("Помилки:", failedResults);Promise.allSettled()Цей метод зручний, коли:
потрібно дочекатися всіх операцій;
помилка однієї операції не повинна приховати результати інших;
потрібно зібрати звіт про успішні та невдалі операції;
виконується пакетна обробка незалежних елементів.
Наприклад, під час надсилання повідомлень кільком користувачам можна зібрати як успішні надсилання, так і помилки.
Promise.race()Promise.race() повертає результат першого промісу, який завершиться — успішно або з помилкою.
const result = await Promise.race([
requestFromPrimaryServer(),
requestFromBackupServer()
]);Якщо першим завершився успішний проміс, Promise.race() виконається. Якщо першим завершився проміс із помилкою, Promise.race() відхилиться.
const delay = (value, milliseconds, shouldFail = false) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldFail) {
reject(new Error(`Помилка: ${value}`));
return;
}
resolve(value);
}, milliseconds);
});
try {
const result = await Promise.race([
delay("повільна операція", 300),
delay("швидка операція", 100)
]);
console.log(result);
// швидка операція
} catch (error) {
console.error(error.message);
}Promise.race()Один із поширених сценаріїв — обмежити час очікування операції:
const timeout = (milliseconds) =>
new Promise((_, reject) => {
setTimeout(() => {
reject(new Error("Операція перевищила ліміт часу"));
}, milliseconds);
});
const delay = (value, milliseconds) =>
new Promise((resolve) => {
setTimeout(() => resolve(value), milliseconds);
});
try {
const result = await Promise.race([
delay("результат сервера", 500),
timeout(200)
]);
console.log(result);
} catch (error) {
console.error(error.message);
}Через 200 мілісекунд буде отримано помилку тайм-ауту.
Promise.race() не скасовує операцію, яка програла гонку. Якщо це важливо, скасування потрібно реалізовувати окремо за допомогою механізмів, які підтримує конкретна асинхронна операція.
Promise.any()Promise.any() шукає перший успішний проміс.
На відміну від Promise.race(), відхилення одного промісу не завершує Promise.any() помилкою. Метод продовжує чекати на інші проміси.
const result = await Promise.any([
requestFromServer("server-1"),
requestFromServer("server-2"),
requestFromServer("server-3")
]);Якщо хоча б один проміс виконається, Promise.any() виконається його значенням.
Якщо всі проміси відхиляться, результуючий проміс відхилиться помилкою AggregateError. У властивості errors міститься масив усіх причин відхилення.
const fail = (message, milliseconds) =>
new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(message));
}, milliseconds);
});
try {
const result = await Promise.any([
fail("Перший сервер недоступний", 100),
fail("Другий сервер недоступний", 200)
]);
console.log(result);
} catch (error) {
if (error instanceof AggregateError) {
console.error("Усі операції завершилися помилкою:");
for (const reason of error.errors) {
console.error(reason.message);
}
}
}Promise.any()Promise.any() підходить, коли:
достатньо одного успішного результату;
є кілька альтернативних серверів або джерел даних;
потрібно використати найшвидший доступний варіант;
окремі помилки не повинні одразу завершувати всю операцію.
| Метод | Коли завершується | Якщо один проміс відхилився | Результат | |---|---|---|---| | Promise.all() | Після успіху всіх або першої помилки | Відхиляється | Масив значень | | Promise.allSettled() | Після завершення всіх | Не відхиляється через окремі помилки | Масив описів результатів | | Promise.race() | Після першого завершеного промісу | Відхиляється, якщо першим була помилка | Значення або помилка | | Promise.any() | Після першого успішного промісу або помилки всіх | Ігнорує окремі помилки | Перше успішне значення |
Наступний приклад можна зберегти у файл combinators.js і запустити командою node combinators.js.
const delay = (value, milliseconds, shouldFail = false) =>
new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldFail) {
reject(new Error(`Операція "${value}" завершилася помилкою`));
return;
}
resolve(value);
}, milliseconds);
});
async function main() {
console.log("Promise.all:");
try {
const allResults = await Promise.all([
delay("користувач", 200),
delay("публікації", 100),
delay("налаштування", 150)
]);
console.log(allResults);
} catch (error) {
console.error(error.message);
}
console.log("\nPromise.allSettled:");
const settledResults = await Promise.allSettled([
delay("профіль", 100),
delay("аватар", 200, true),
delay("друзі", 150)
]);
for (const result of settledResults) {
if (result.status === "fulfilled") {
console.log("Успіх:", result.value);
} else {
console.log("Помилка:", result.reason.message);
}
}
console.log("\nPromise.race:");
try {
const firstResult = await Promise.race([
delay("повільний сервер", 300),
delay("швидкий сервер", 100)
]);
console.log("Першим завершився:", firstResult);
} catch (error) {
console.error(error.message);
}
console.log("\nPromise.any:");
try {
const firstSuccessfulResult = await Promise.any([
delay("недоступний сервер", 100, true),
delay("резервний сервер", 200),
delay("повільний сервер", 300)
]);
console.log("Перший успішний результат:", firstSuccessfulResult);
} catch (error) {
console.error("Усі операції завершилися помилкою");
for (const reason of error.errors) {
console.error(reason.message);
}
}
}
main().catch((error) => {
console.error("Непередбачена помилка:", error);
});Combinators мають спеціальну поведінку для порожнього масиву:
Promise.all([]) виконується зі значенням [];
Promise.allSettled([]) виконується зі значенням [];
Promise.race([]) залишається в стані очікування назавжди;
Promise.any([]) відхиляється з AggregateError.
Тому перед передаванням динамічного списку варто враховувати можливість, що він буде порожнім.
Promise.all() для частково необов’язкових операційЯкщо одна з операцій може не виконатися, але результати інших усе одно потрібні, Promise.all() може бути невідповідним вибором. У такій ситуації краще використати Promise.allSettled().
race() та any()Promise.race() реагує на перший завершений проміс незалежно від його результату.
Promise.any() реагує лише на перший успішний проміс:
перша помилка для race() може завершити всю операцію;
перша помилка для any() лише ігнорується, якщо є інші проміси.
Комбінатор керує лише результуючим промісом. Він не гарантує скасування інших операцій після завершення all(), race() або any().
Якщо використовується Promise.any() і всі проміси відхилилися, потрібно перевіряти error.errors, а не очікувати звичайну одиничну помилку.
Такий код виконує операції послідовно:
const user = await loadUser();
const posts = await loadPosts();
const settings = await loadSettings();Якщо операції незалежні, їх краще запустити паралельно:
const [user, posts, settings] = await Promise.all([
loadUser(),
loadPosts(),
loadSettings()
]);Це може суттєво зменшити загальний час очікування.
Promise.all() чекає на успішне виконання всіх промісів і відхиляється через першу помилку.
Promise.allSettled() чекає на завершення всіх промісів і повертає результат кожного з них.
Promise.race() повертає результат першого завершеного промісу — успішного або відхиленого.
Promise.any() повертає перший успішний результат і відхиляється лише тоді, коли всі операції завершилися помилкою.
Результати Promise.all() зберігають порядок вхідного масиву, навіть якщо операції завершуються в іншому порядку.
Combinators не скасовують асинхронні операції, які вже були запущені.