Почему JS однопоточный, но неблокирующий? Promise.all и race

Как один поток справляется с асинхронностью и какие комбинаторы промисов спросят.

JavaScript выполняет код в одном потоке, но долгие операции (сеть, таймеры, файлы) делегирует среде (браузеру/Node), а результаты обрабатывает через очередь — поэтому он не «висит».

Один поток, но без блокировки

В JS только один call stack — в каждый момент выполняется одна строка кода. Но операции ввода-вывода выполняет не сам движок, а окружение. Пока «грузится» запрос, движок свободен и делает другую работу; когда результат готов, его коллбэк попадает в очередь.

Поэтому тяжёлый синхронный код блокирует всё (зависает интерфейс), а асинхронные операции — нет.

console.log("старт");
// синхронный тяжёлый цикл блокирует поток
let sum = 0;
for (let i = 0; i < 1000000; i++) sum += i;
console.log("посчитали:", sum);
console.log("конец");

Вывод:

старт
посчитали: 499999500000
конец

Promise.all — ждём все

Promise.all ждёт, пока выполнятся все промисы, и возвращает массив результатов. Если хоть один упал — весь all падает.

const p1 = Promise.resolve("user");
const p2 = Promise.resolve("orders");
const p3 = Promise.resolve("settings");

Promise.all([p1, p2, p3]).then((results) => {
  console.log(results);
});

Вывод:

[ 'user', 'orders', 'settings' ]

Promise.race — кто первый

Promise.race возвращает результат первого завершившегося промиса (успех или ошибка). Удобно для таймаутов.

const fast = Promise.resolve("быстрый");
const slow = new Promise((res) => setTimeout(() => res("медленный"), 50));

Promise.race([fast, slow]).then((winner) => {
  console.log("победил:", winner);
});

Вывод:

победил: быстрый

allSettled и any

Promise.allSettled дожидается всех и возвращает статусы (не падает от одной ошибки). Promise.any возвращает первый успешный.

const ok = Promise.resolve("успех");
const fail = Promise.reject("ошибка");

Promise.allSettled([ok, fail]).then((res) => {
  console.log(res.map((r) => r.status));
});

Promise.any([fail, ok]).then((v) => console.log("any:", v));

Вывод:

[ 'fulfilled', 'rejected' ]
any: успех

Памятка по комбинаторам

МетодКогда разрешается
allвсе успешны (иначе падает на первой ошибке)
raceпервый завершившийся (успех ИЛИ ошибка)
allSettledвсе завершились, со статусами
anyпервый успешный

Итог

  • JS однопоточный: тяжёлый синхронный код блокирует, асинхронный — нет.
  • all ждёт всех и падает от одной ошибки; race — первый завершившийся.
  • allSettled вернёт статусы всех; any — первый успешный.
Проверьте себя
1. Почему однопоточный JavaScript не «зависает» на сетевых запросах?
AОн создаёт новые потоки
BДолгие операции делегируются окружению, а результаты обрабатываются через очередь
CЗапросы выполняются мгновенно
DJS на самом деле многопоточный
2. Что вернёт Promise.race?
AМассив всех результатов
BРезультат первого завершившегося промиса (успех или ошибка)
CТолько успешные результаты
DВсегда первый по списку
3. Чем Promise.allSettled отличается от Promise.all?
AНичем
BallSettled дожидается всех и не падает от ошибки, возвращая статусы
CallSettled берёт только первый промис
DallSettled работает только с одним промисом
Поддержать проект