Куда пойдет выполнение при конкурентном запуске нескольких задач в JavaScript
Что проверяют
Понимаете ли вы разницу между конкурентностью и параллельностью в JavaScript и порядок, в котором event loop разбирает задачи. Следом обычно просят запустить три запроса одновременно и обработать ошибку одного из них или спрашивают про гонку ответов в поиске.
Как отвечать
Основа: ваш JavaScript выполняется в одном потоке. Параллельно идут только операции браузера: сеть, таймеры, чтение файлов. Их результаты возвращаются колбэками в тот же поток по очереди. Порядок одного витка event loop:
- синхронный код текущей задачи до конца;
- все микрозадачи: обработчики промисов, продолжения после
await,queueMicrotask. Микрозадачи, добавленные по ходу, выполняются в этом же проходе; - отрисовка, если браузер решил обновить кадр, перед ней
requestAnimationFrame; - одна макрозадача:
setTimeout, событие,MessageChannel, и снова все микрозадачи.
Для запуска нескольких задач конкурентно есть комбинаторы:
Promise.all: ждёт все, отклоняется на первой ошибке;Promise.allSettled: ждёт все и отдаёт статус каждой;Promise.race: первый завершившийся, успешно или с ошибкой;Promise.any: первый успешный.
Три запроса, вызванные до await Promise.all([...]), идут по сети одновременно, а их обработчики выполнятся по одному в порядке прихода ответов. Настоящую параллельность вычислений дают только Web Workers.
Где спотыкаются
Пишут await внутри цикла for и получают последовательные запросы там, где хотели одновременные. Или используют forEach с async-колбэком, который никто не ждёт.
Считают, что Promise.all отменяет остальные запросы при ошибке. Он только перестаёт их ждать, а запросы доходят до конца. Для отмены нужен AbortController.
Забывают про гонку ответов: в поиске с автоподсказками старый медленный ответ приходит после нового и затирает результат. Лечится отменой прошлого запроса или проверкой, что ответ относится к последнему вводу.