Асинхронность, микрозадачи и Event loop в одном вопросе
Maksym PohribniakЭтот популярный вопрос на собеседованиях по JavaScript встречается в разных вариациях, но суть примерно одинакова.
Прочитайте следующий пример кода и скажите в каком порядке произойдет вывод сообщений.

Вероятно вы знаете, или слышали что-то про v8, однопоточность, коллбеки и промисы. Но уверенны ли вы что знаете как на самом деле как выполняется JavaScript?
Вывод в консоли будет следующим:
script start // строка 1
end script // строка 7
promise then // строка 5
timeout // строка 3
Проверить это можно по ссылке.
Чтобы ответить верно на данный вопрос и вопросы похожего типа нам нужно
разобраться во всех тех терминах описанных в заголовке.
Начнем с того, почему "script start" и "end script" выведены в первую очередь?
С первой строкой все ясно — так, как скрипт начинается с этого кода и интерпретируется сверху вниз — логично что первым он и исполнится. Но почему после него следует "end script"?
Все дело в том что в коде выше представлены синхронные и асинхронные операции.
Большее количество кода которое пишет JS разработчик, является синхронным, то есть — подвешивает весь процесс или команду до тех пор, пока не выполнится, и лишь затем передает управление дальше, нижестоящему коду. Например мы всегда уверенны в том, что в следующем коде:

Переменная b будет вмещать значение 10 так как выше мы определили переменную а и умножили её на число 2.
В свою очередь асинхронный код это операции требующие от системы обратиться к внешнему устройству API например setTimeout, AJAX запросы, DOM events. Или же Promise, async/await и генераторы являющиеся частью языка начиная с ES6/ES7. Пример асинхронного кода:

AJAX запрос выполнен через встроенный в браузер XMLHttpRequest.
Если мы используем его асинхронную версию, то не сможем получить доступ к полученным данным сразу же после выполнения метода .send()
Асинхронный код передает управление дальше, но точка остановки запоминается и управление к ней возвращается в будущем при каком-то условии. Например при подписке на событие "onload" или "onreadystatechange".
Главным отличием синхронных от асинхронных операций:
- Пока выполняется синхронный код — никакой асинхронный выполнятся не может. В нашем примере выполняется сначала весь синхронный код (console.log-и), а только потом асинхронный (Promise.then и setTimeout).
- Синхронный код является блокирующим. Пока происходит синхронная операция страница "подвисает" не реагирует на любые события. Например при использовании цикла на миллион итераций вы не сможете взаимодействовать со страницей какое-то время. Именно по этому тяжелые операции типа обработки изображений, операций с файлами, создание запросов сети должны быть асинхронными. Ведь мы можем исполнять другой полезный код пока ждем коллбек или промис.
И тут появляется Event Loop (или цикл обработки событий).
JavaScript код в браузере выполняется в одном потоке. Движок (v8) не имеет возможности поставить обработку события на паузу, перейти на другое действие, а впоследствии — восстановить выполнение первого. Все действия обрабатываются в порядке очереди. Для выполнения кода выделяется область памяти — stack (стэк). Из списка происходящих событий формируется очередь. Как только стэк освобождается — движок может выполнять отложенный код (из очереди). За вызов процессов из очереди и отвечает event loop.
Если вы хотите более детально разобраться с тем, как он работает — рекомендую следующее видео или статью.
Мы определили, что синхронный код выполняется в первую очередь, но почему же Promise.resolve().then() выполнился раньше чем setTimeout, он ведь записан ниже. Напомню порядок выполнения:

Дело в том, что для движка существует два типа задач — макрозадачи (macrotasks) и микрозадачи (microtasks). Из документации:
Macrotasks: setTimeout, setInterval, setImmediate, requestAnimationFrame, I / O, отображение пользовательского интерфейса
Microtasks: process.nextTick, Promises, Object.observe, MutationObserver
Какой будет порядок в нашем случае?
- Оба console.log() появляются первым, т.к. это синхронные вызовы.
- promise выполняется вторым т. к. это микрозадача и выполняется после текущего синхронного кода
- setTimeout выполняется последним, т. к. макрозадача
Более детальный разбор задач можете посмотреть здесь.