Несколько промисов сразу
Представьте, что повару нужно испечь два пирога. Можно поставить в духовку первый, дождаться его и только потом поставить второй. А можно поставить оба сразу: тогда ждать придётся один раз, а не два.
Два await подряд работают по первому способу. Второй таймер ставится только после того, как сработал первый:
function wait(ms, value) {
return new Promise((resolve) => setTimeout(() => {
console.log("ready", value);
resolve(value);
}, ms));
}
async function main() {
await wait(100, "a");
await wait(100, "b");
}
main();
// ready a
// ready bРазберём, что происходит:
mainвызываетwait(100, "a")и ждёт на первомawait.- Через 100 миллисекунд таймер печатает
ready a, и промис выполняется. - Только теперь начинается вызов
wait(100, "b"), и ещё через 100 миллисекунд печатаетсяready b.
Всего проходит около 200 миллисекунд. Когда второе ожидание не зависит от первого, такой порядок только теряет время. Оба промиса можно запустить сразу и ждать их вместе.
Promise.all
Promise.all принимает массив промисов и возвращает один промис. Этот промис выполняется, когда выполнены все промисы массива, и его значение — массив их значений. Порядок значений совпадает с порядком в исходном массиве, а не с тем, кто закончил первым:
async function both() {
const [a, b] = await Promise.all([wait(200, "slow"), wait(100, "fast")]);
console.log(a, b);
}
both();
// ready fast
// ready slow
// slow fastДавайте разберём по шагам:
- Оба вызова
waitвыполняются сразу, ещё доPromise.all, и оба таймера начинают отсчёт. - Через 100 миллисекунд срабатывает таймер
fastи печатаетready fast. - Через 200 миллисекунд срабатывает таймер
slowи печатаетready slow. - Теперь выполнены оба промиса, и
awaitдаёт массив["slow", "fast"].
В результате "slow" стоит первым, как в исходном массиве, хотя готов он позже. Поэтому деструктуризация раскладывает значения по именам без путаницы. Так повар подаёт блюда в порядке заказа, а не в порядке готовности.
Сам Promise.all ничего не запускает: таймеры начинают отсчёт в момент вызова wait, ещё до него. Promise.all только ждёт промисы, которые уже созданы. Если массив пустой, промис из Promise.all сразу выполняется со значением [].
Это частый вопрос на собеседовании: как дождаться нескольких независимых промисов быстрее, чем по очереди? Ответ: создать их все сразу и ждать через Promise.all.
Первый отказ
Если хотя бы один промис отклонён, промис из Promise.all сразу отклоняется с его причиной и остальных не ждёт:
async function failFast() {
try {
await Promise.all([wait(100, "a"), Promise.reject(new Error("b failed"))]);
console.log("all done");
} catch (e) {
console.log(e.message);
}
}
failFast();
// b failed
// ready aОтказ Promise.all бросается на строке с await, как отказ одного промиса, поэтому его ловит try…catch. Строка all done не печатается, а ошибка появляется раньше, чем срабатывает таймер a.
Здесь аналогия с поваром перестаёт работать. Повар, у которого сгорел один пирог, может вынуть из духовки и второй. А Promise.all ничего не отменяет: таймер a всё равно печатает ready a, но его значение уже никто не получит.
Асинхронный код вы отрабатываете и в задачах курса. Повтор вызова через await и try…catch вы уже написали в задаче перед этим уроком. После теста к нему вас ждёт задача на промис с ограничением времени.
Частая ошибка
Промисы передали отдельными аргументами, а не одним массивом:
Promise.all(wait(100, "a"), wait(100, "b")); // ✗TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))Promise.all берёт только первый аргумент и пытается обойти его, как массив. Первый аргумент — промис, а не массив, и перебрать его по элементам нельзя. Слово object в сообщении и означает этот промис. Хвост в скобках — внутреннее имя способа перебора, его можно не читать. Оберните промисы в квадратные скобки: Promise.all([wait(100, "a"), wait(100, "b")]).
Главное
- Два
awaitподряд ждут по очереди, а независимые промисы можно создать сразу и ждать черезPromise.all. - Значения из
Promise.allидут в порядке исходного массива, а не в порядке готовности. - Первый же отказ сразу отклоняет промис из
Promise.all, но остальная работа не отменяется. Promise.allпринимает один массив, а не промисы отдельными аргументами.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.