Каждый JS-разработчик, или тот, кто хочет им стать, сталкивался или на собеседованиях, или на разборах собесов про задачки на событийный цикл. Сначала интервьюер спрашивает кратко про event loop, затем показывает кусок кода, где обычно есть несколько console.log(), и нас просят сказать очередность появления логов. Далее, дается ответ, и если он правильный, идут дальше, а если нет, интервьюер скажет свою последовательность (возможно даст небольшой комментарий) и также двинутся дальше. Очень редко, если вы ошиблись, вам подробно объяснят, что как и почему. Все-таки - собеседование.

Несколько месяцев назад моя супруга захотела перейти в другую компанию, и начала проходить собесы. И вот на после одного из них, я понял, что не всегда могу грамотно объяснить почему именно сейчас какая таска сработает, и поэтому решил углубиться в понимании и сейчас вам представлю мои размышления. Прошу воспринимать этот материал как открытый тред для рассуждения с некими вводными.

Event loop

Лучше, чем на https://learn.javascript.ru/event-loop теорию я не объясню, так что давайте сразу перейдем к задачкам.

Сначала разберем две задачки из статьи выше, а затем я покажу мои размышления по другим задачкам с реальных собесов.

Принцип решения задач на Event loop

Основное принцип в решении задачек на событийный цикл.

  1. Выполняется основной поток кода (+ выполняются скрипты в теле создания промисов)

  2. Выполняются микротаски
    По факту, микротаски = промисы.
    Также есть возможность принудительно микромизировать задачу с помощью queueMicrotask(f), но я так никогда не делал в рабочем коде. Если у кого есть опыт - пожалуйста, поделитесь.
    (важно помнить, что исполняются ВСЕ промисы, и нужно об этом помнить, так как по факту, так можно застопорить процесс выполнения скриптов и очень не скоро приступить к макротаскам)

  3. Выполняется макротаска
    Макротаска - это у нас или браузерное API, или манипуляции с DOM деревом (дополните меня в комментариях, пожалуйста)


    Далее, цикл повторяется.
    Если основной поток все и микрозадач тоже нет, последовательно выполняются макротаски.

Как я предлагаю решать задачи на event loop

В ходе решения задачек, я пришел к выводу, что можно использовать вот такую табличку, и с ее помощью неплохо упрощать себе жизнь.

Не стесняйтесь на собеседованиях использовать ее, лучше даже от руки, на бумажке.
Решение в голове, в стрессовой ситуации - большой шанс совершить ошибку.

Основной поток

Микрозадачи

Макрозадачи

Заполняйте табличку так, чтобы каждый скрипт, был на отдельной строке! это важно.
Ну, давайте приступим. У нас есть простенькая задачка, уровня Junior.

ЗАДАЧА 1

setTimeout(function timeout() {
console.log('Таймаут');
}, 0);

let p = new Promise(function(resolve, reject) {
console.log('Создание промиса');
resolve();
});

p.then(function(){
console.log('Обработка промиса');
});

console.log('Конец скрипта');

Идем сверху-вниз, именно так, как это делает парсер нашего кода.

setTimeout(function timeout() { console.log('Таймаут'); }, 0);

Сначала, видим setTimeout, это макрозадача (браузерное API), и мы должны его зарегистрировать (если не понятно что такое регистрация, предлагаю посмотреть это видео).

Помимо занесения результата выполнения скрипта в нашу табличку, укажем и время, через которое он должен сработать (время не точно, но гарантирующее задержку, то есть он сработает, не раньше, чем через N секунд).

Основной поток

Микрозадачи

Макрозадачи

'Таймаут',0

let p = new Promise(function(resolve, reject)
{ console.log('Создание промиса');
resolve(); });

Заметим, что здесь у нас создается промис, console.log('Создание промиса') выполнится, т.к. это по сути основной поток, нам не важно, как завершится промис.

Основной поток

Микрозадачи

Макрозадачи

'Таймаут',0

'Создание промиса'

p.then(function(){ console.log('Обработка промиса'); });

А тут, мы видим, что наш промис уже исполняется, видим цепочку. Следовательно, это микрозадача.

Основной поток

Микрозадачи

Макрозадачи

'Таймаут',0

'Создание промиса'

'Обработка промиса'

И финальное, console.log('Конец скрипта');
Это основной поток.

Основной поток

Микрозадачи

Макрозадачи

'Таймаут',0

'Создание промиса'

'Обработка промиса'

'Конец скрипта'

А дальше, остается самое простое и веселое. Собрать наш ответ, как бургер, по методичке.

Идем по нашему гайду:

  1. Основной поток (все задачи)

  2. Микрозадачи (все задачи)

  3. Макрозадача

  4. Repeat, please.

Итак, у нас получается

  1. 'Создание промиса'

  2. 'Конец скрипта'

  3. 'Обработка промиса'

  4. 'Таймаут'

Пруфы
Пруфы

ЗАДАЧА 2

Окей, давайте сразу решим вторую задачку, посложнее.

console.log(1);

setTimeout(() => console.log(2));

Promise.resolve().then(() => console.log(3));

Promise.resolve().then(() => setTimeout(() => console.log(4)));

Promise.resolve().then(() => console.log(5));

setTimeout(() => console.log(6));

console.log(7);

console.log(1) - основной поток выполнения кода

Основной поток

Микрозадачи

Макрозадачи

console.log(1)

setTimeout(() => console.log(2)) - регистрируем макрозадачу в браузерное API, с нулевым сроком срабатывания

Основной поток

Микрозадачи

Макрозадачи

console.log(1)

console.log(2), 0

Promise.resolve().then(() => console.log(3)) - микрозадача

Основной поток

Микрозадачи

Макрозадачи

console.log(1)

console.log(2), 0

console.log(3)

Promise.resolve().then(() => setTimeout(() => console.log(4)))

Тут интереснее, промис порождает макрозадачу. Для таких особых задачек, я добавляю стрелочку направо, мол, при исполнении промиса, перейдет в статус макрозадач. Не забываем, что есть нулевая отсрочка срабатывания

Основной поток

Микрозадачи

Макрозадачи

console.log(1)

console.log(2), 0

console.log(3)

console.log(4),0 =>

Promise.resolve().then(() => console.log(5)) - микротаска

Основной поток

Микрозадачи

Макрозадачи

console.log(1)

console.log(2), 0

console.log(3)

console.log(4),0 =>

console.log(5)

setTimeout(() => console.log(6)) - макрозадача с нулевой отсрочкой срабатывания.

Основной поток

Микрозадачи

Макрозадачи

console.log(1)

console.log(2), 0

console.log(3)

console.log(4),0 =>

console.log(5)

console.log(6), 0

И финальная, console.log(7) - основной поток.

Основной поток

Микрозадачи

Макрозадачи

console.log(1)

console.log(2), 0

console.log(3)

console.log(4),0 =>

console.log(5)

console.log(6), 0

console.log(7)

Фух, заполнили. Давайте собирать наш ответ. Опять покажу наш гайд:

  1. Основной поток (все задачи)

  2. Микрозадачи (все задачи)

  3. Макрозадача

  4. Repeat, please.

Итак, у нас получается

  1. console.log(1)

  2. console.log(7)

  3. console.log(3)

  4. console.log(5)

  5. console.log(2)

  6. console.log(6)

  7. console.log(4)

Тут нужно учесть, что console.log(4),0 встанет в конец очереди макрозадач. А там у нас уже находятся 2, 6, поэтому 4 идет в конец.

Пруф
Пруф

ЗАДАЧА 3

Окей, а теперь давайте решим похожую задачку с собеседования в один очень интересный, прогрессивный российский банк. У кого есть догадки, пишите в комменты.

Она очень похожа, но есть один нюанс.

console.log(1);

setTimeout(() => console.log(2));

Promise.reject(3).catch(console.log);

new Promise(resolve => setTimeout(resolve)).then(() => console.log(4));

Promise.resolve(5).then(console.log);

console.log(6);

setTimeout(() => console.log(7),0);

Прошу обратить внимание, на некоторую разницу в том, как записаны промисы, и будьте уверены, они так тоже прекрасно срабатывают и значения передадутся в функции console.log

Самый интересный кусочек здесь, это

new Promise(resolve => setTimeout(resolve)).then(()=>console.log(4))

Важно помнить!

Функция, переданная в конструкцию new Promise, называется исполнитель (executor). Когда Promise создаётся, она запускается автоматически.

Я уже пропущу подробное заполнение таблицы, надеюсь оно понятно.

console.log(1);

setTimeout(() => console.log(2));

Promise.reject(3).catch(console.log);

new Promise(resolve => setTimeout(resolve)).then(()=>console.log(4))

Вот тут хитрее. В момент выполнения executor'а регистрируется макротаска, при выполнении которой регистрируется микротаска.

Основной поток

Микрозадачи

Макрозадачи

console.log(1)

console.log(2),0

console.log(3)

setTimeout(resolve)).then(()=>console.log(4))

Ну и добьем нашу табличку, и будем собирать ответ.

solve(5).then(console.log);

console.log(6);

setTimeout(() => console.log(7),0);

Основной поток

Микрозадачи

Макрозадачи

console.log(1)

console.log(2),0

console.log(3)

setTimeout(resolve)).then(()=>console.log(4))

console.log(5)

console.log(6)

console.log(7),0

Ну что, надеюсь пока все понятно. Начинаем аккуратно собирать ответ.

Опять же наш гайд. Надеюсь вы его уже выучили.

  1. Основной поток (все задачи)

  2. Микрозадачи (все задачи)

  3. Макрозадача

  4. Repeat, please.

Основной поток

Микрозадачи

Макрозадачи

console.log(1)

console.log(2),0

console.log(3)

console.log(5)

console.log(4)

setTimeout(resolve)).then(()=>console.log(4))

console.log(6)

console.log(7),0

Итак, у нас получается 1 6 3 5 2 ...

Тут все ясно понятно. А вот что с четверкой? Макрозадача, порождает микрозадачу.
Когда очередь подходит к нашей хитрой четверке, то вспоминаем, что макрозадачи срабатывают не все, а лишь по одной, а потом цикл проверяется очередь основного потока и микрозадач. А у нас получается, что макрозадача порождает микрозадачу, выполняет ее, и лишь потом берет следующую макрозадачу. Поэтому можно еще раз перерисовать табличку.

Итого, наш ответ 1 6 3 5 2 4 7

Пруф
Пруф

1746 это некий случайный идентификатор. Он к нашему ответу отношения не имеет.
Когда что-то выполняешь вручную в консоли, результат последней синхронной операции записывается в консоль. Здесь последним был setTimeout, который вернул id созданного таймаута (тот самый, на который можно натравить clearTimeout).

(данный отрывок взял из комментов, спасибо аудитории).

ЗАДАЧА 4

Отлично! и давайте на закрепление, еще одну задачку. Она так сказать с маленькой звездочкой, но не пугайтесь ее.

const myPromise = (delay) => new Promise((res, rej) => { setTimeout(res, delay) })
setTimeout(() => console.log('in setTimeout1'), 1000);
myPromise(1000).then(res => console.log('in Promise 1'));
setTimeout(() => console.log('in setTimeout2'), 100);
myPromise(2000).then(res => console.log('in Promise 2')); 
setTimeout(() => console.log('in setTimeout3'), 2000);
myPromise(1000).then(res => console.log('in Promise 3'));
setTimeout(() => console.log('in setTimeout4'), 1000);
myPromise(5000).then(res => console.log('in Promise '));

Итого, у нас сначала есть функция myPromise, которая принимает параметром время ожидания, а далее конструирует промис, который выполнит функцию через некоторое время ожидания. Этот кусочек кода бояться перестали, идем дальше.

У нас чередуются классические макротаски, с созданными, через функцию myPromise.
Ну ничего страшного, решим.

Также, в момент обновления статьи, я решил, немного дополнить "мой метод объяснения". Вроде бы я сам говорю, давайте на бумажке все распишем, а в итоге один пункт делаю в голове, а именно регистрацию в WEB.API. Возможно для простых задач и ок, но в сложных уже можно если не ошибиться, то не совсем верно объяснить, и как верно подметили в комментах, "натягивать" ответ на решение. Не надо так =).

1. setTimeout(() => console.log('in setTimeout1'), 1000 ) - уходит в Web.Api

WEB.API

() => console.log('in setTimeout1'), 1000

Основной поток

Микрозадачи

Макрозадачи

Обращу внимание на то, что пока у нас задачи зарегистрированы в WEB.Api, они не переходят в наши очереди.

const myPromise = (delay) => new Promise((res, rej) => { setTimeout(res, delay) })
myPromise(1000).then(res => console.log('in Promise 1'))

Конструкция new Promise, выполнится сразу же при создании, а значит результатом, будет setTimeout, который сначала пойдет в Web.api, потом станет макротаской, которая в свою очередь породит микрозадачу.

Итого, myPromise(1000) возвращает setTimeout, который сначала надо зарегистрировать в Web.api.

WEB.API

() => console.log('in setTimeout1'), 1000

res1, 1000

Заполним далее. Так как у нас все задачи это setTimeout (на верхнем уровне), то давайте заполним до конца web.Api

res1...4 это функции, которые внутри себя содержат микротаски, но после web.api попадут сначала в очередь макротасок.

WEB.API

() => console.log('in setTimeout1'), 1000

res1, 1000

() => console.log('in setTimeout2'), 100

res2, 2000

() => console.log('in setTimeout3'), 2000

res3, 1000

() => console.log('in setTimeout4'), 1000

res4, 5000

Итак, все задачки находятся в web.api. Давайте начнем их исполнять. Первой уходит та, у которой закончилось время простоя, это 'in setTimeout2'

setTimeout(() => console.log('in setTimeout2'), 100)
Она переходит в очередь макрозадач, и так как очередь микротасок и основного потока - пусты, исполняется.

Основной поток

Микрозадачи

Макрозадачи

'in setTimeout2'

WEB.API

() => console.log('in setTimeout1'), 1000

res1, 1000

res2, 2000

() => console.log('in setTimeout3'), 2000

res3, 1000

() => console.log('in setTimeout4'), 1000

res4, 5000

По факту, исполнение макрозадачи, в нашей системе табличек, можно назвать переходом в основной поток. Так, как у нас посреди решения нашей задачи, нового ничего не приходит, я думаю так сделать законно. Только помним, она уже выполнена. И очередь пуста. Мы это делаем только для того, чтобы потом легко собрать ответ. Давайте пометим ее неким символом, чтобы помнить, что задача уже выполнена.

Основной поток

Микрозадачи

Макрозадачи

'in setTimeout2' ????

  • () => console.log('in setTimeout1'), 1000

  • res1, 1000

  • res3, 1000

  • () => console.log('in setTimeout4'), 1000

Все они переходят в очередь макрозадач.

Основной поток

Микрозадачи

Макрозадачи

'in setTimeout2' ????

'in setTimeout1'

res1

res3

'in setTimeout4'

WEB.API

res2, 2000

() => console.log('in setTimeout3'), 2000

res4, 5000

Макрозадачи выполняются по одной. Сначала уходит 'in setTimeout1';
Затем, наступает очередь res1, но помним, что внутри есть исполнения промиса, а это значит что она переходит в очередь микротасок и сразу же там исполняется (реальная очередь основного потока пуста, очередь микротасок пуста).
То же самое будет и с res3. А in setTimeout4' выполнится как обычная макрозадача.

Основной поток

Микрозадачи

Макрозадачи

'in setTimeout2' ????

'in setTimeout1'????

res1

res3

'in setTimeout4'

Да, и сейчас уже можно вспомнить, что res1 => console.log('in Promise 1');
Итого, получаем:

Основной поток

Микрозадачи

Макрозадачи

'in setTimeout2' ????

'in setTimeout1'????

'in Promise 1'????

'in Promise 3'????

'in setTimeout4'????

Итак, у нас следующая партия задачек выходит из web.api

  • res2, 2000

  • () => console.log('in setTimeout3'), 2000

Они обе сначала попадают в очередь макротасок.
Затем res2 (in Promise2) переходит в очередь микротасок, исполняется и затем исполняется 'in setTimeout3'.

Основной поток

Микрозадачи

Макрозадачи

'in setTimeout2' ????

'in setTimeout1'????

'in Promise 1'????

'in Promise 3'????

'in setTimeout4'????

'in Promise 2'????

'in setTimeout3'????

И остается единственная задачка, оставшаяся в web.api

WEB.API

res4, 5000

Когда подходит время ее исполнения, она переходит в очередь макрозадач, тк она единственная во всем списке очередей (не считая исполненных, со смайликом), она выполняется. Переходит из макрозадач - в микрозадачи, и сразу же исполняется.

Итого, мы получаем уже и готовый ответ:

Основной поток

Микрозадачи

Макрозадачи

'in setTimeout2' ????

'in setTimeout1'????

'in Promise 1'????

'in Promise 3'????

'in setTimeout4'????

'in Promise 2'????

'in setTimeout3'????

'in Promise'????

Пруф
Пруф

Также, в комментариях, один наш коллега, дал очень классную гифку, которая показывает решение этой задачи. Немного велика скорость, но думаю в сумме с объяснениями выше, сомнений больше быть не должно.

Итого, я очень надеюсь, что подход с табличками будет полезным для junior+, middle разработчиков при прохождении собеседований.

Будет очень круто, если вы покидаете в комменты интересных и сложных задачек по этой теме со своих собесов. Может кто сам собесит и готов поделиться.

P.S. кто знает как таблицы копировать? а то я вручную их все заполнял... Что-то не разобрался.

Комментарии (21)


  1. Mox
    11.08.2022 00:24
    +1

    Полезный текст для прохождения собеседований.

    Но, честно говоря, не хочется тащить в прод такой код, где прям нужны такие знания или идет опора в определении последовательность операций именно на то как работает event loop


    1. fransua
      11.08.2022 11:15

      Скорее всего он уже в проде внутри реакта/ангуляра/вью


    1. jershell
      11.08.2022 11:51
      +2

      Эти знания нужны чаще в момент отладки, чтоб можно было объяснить себе и всем заинтересованным почему оно работает именно так. Часто бывает что мы что-то грузим в 3-5 местах и вдруг что-то загружается раньше времени и вот тут как раз понимание ивент лупа очень кстати.


  1. Alexandroppolus
    11.08.2022 00:38
    +5

    1746 это некий случайный идентификатор. Он к нашему ответу отношения не имеет.
    Если кто может более точно объяснить почему он тут появляется, прошу в комментарии.

    Когда что-то выполняешь вручную в консоли, оно пишет результат последней синхронной операции. Здесь последним был setTimeout, который вернул id созданного таймаута (тот самый, на который можно натравить clearTimeout). Да, это просто число.


  1. Zibx
    11.08.2022 01:46
    +4

    В продакшене я бы не стал на такое полагаться. Даже на то что два сетТаймаута с одинаковой задержкой выполнятся в порядке их создания — не надо полагаться.

    На собеседовании хорошо при таких вопросах отстоять позицию того что есть сейчас, а есть «потом» и это «потом» будет синхронизировано через более надёжные механизмы. Если работодатель настаивает, то можно выполнить код в консоли. Если хочет решения на бумажке, то встать и уйти, нервы дороже.


  1. Rodionbgd
    11.08.2022 08:50

    Интересно освежить в памяти.

    В задаче 3 название колонок 2 и 3 перепутаны.


    1. jakut_bmstu Автор
      11.08.2022 08:54

      исправил


  1. blindfox
    11.08.2022 08:50

    1746 - айди от setTimeout, который можно использовать в clearTimeout, дабы удалить таймер

    вообще интересная статья
    если интересно, то можешь еще добавить requestAnimationFrame
    и + есть подвохи в триггерных кликах https://jsfiddle.net/e5fqrapb/


  1. tishka85
    11.08.2022 08:53

    В 4 задаче не сходится. Все setTimeout в функции myPromise будут идти и регистрироваться синхронно, в потоке основного кода. Соответственно всё что вы занесли в микрозадачи должны по идее быть в макрозадачах. Затем, когда финально собираем ответ:

    1. Срабатывает in 'setTimeout2' через 100 мс

    2. 'in setTimeout1' через 1000

    3. Через 1000 мс резолвится первый промис (запускается колбек, который добавляет в очередь микротасок колбек с console.log('in Promise 1'))

    4. Далее цикл событий видит, что в очереди микротасок появилось задание, он тут же его выполняет (console.log('in Promise 1'))

    5. Очередь микротасок пуста, следовательно возвращаемся к макротаскам => Через 1000 мс резолвится второй промис (запускается колбек, который добавляет в очередь микротасок колбек с console.log('in Promise 3'))

    6. Далее цикл событий видит, что в очереди микротасок появилось задание, он тут же его выполняет (console.log('in Promise 3'))


    и т.д.


    1. jakut_bmstu Автор
      11.08.2022 08:54

      не совсем понял, что ты имеешь ввиду под словом "не сходится". Порядок выполнения вроде верный.


      1. tishka85
        11.08.2022 09:34

        сейчас попробую объяснить. Просто такое ощущение, что ты не до конца разобрался с этой задачей и в итоге не совсем правильно объяснил последовательность и механику, каким образом этот код будет выполняться (проще говоря "подогнал" ответ под решение). Соответственно, новички, читающие статью, могут либо запутаться в этом примере либо вообще не понять в итоге, как там всё происходит.

        Смотри,

        По факту, у нас выполнятся все микрозадачи, и они займут свое место в очереди макрозадач

        тебя не смутило, что после этой фразы микрозадачи не добавляются по одной в конец очереди макрозадач (как это должно быть и как это было в задаче 2 с console.log(4), ты там сам писал, что она идет в конец очереди макрозадач и это правильно, тут не поспоришь), вместо этого они как бы перемешиваются с уже существующими в очереди макрозадачами, передвигаясь просто вправо.

        Ответ прост: ИЗНАЧАЛЬНО все задачи в очереди микрозадач не должны быть в колонке с микрозадачами. Они должны идти в колонке с макрозадачами, потому что как я уже писал:

        Все setTimeout в функции myPromise будут идти и регистрироваться синхронно, в потоке основного кода

        И только потом, при разборе очереди макрозадач по одной на небольшой промежуток времени эти задачи попадают в список микрозадач (так как резолвится этот промис) и тут же выполняются (так как после каждой макротаски идет выполнение микротасок - при наличии их в очереди). Как раз это я показал в первом комментарии, в своей последовательности (список действий не полный, так как потом происходят аналогичные действия, смысла нет повторять)


        1. jakut_bmstu Автор
          11.08.2022 10:20

          Соглашусь, надо поправить, почему-то действительно биполярочка. Спасибо, что подметил!


        1. jakut_bmstu Автор
          11.08.2022 11:38

          Поправил. Однако знаешь, я вот не до конца понял момент. Ты говоришь, что они срабатывают в основном потоке. Ну тогда, это значит, что после их срабатывания, они должны будут также пойти в конец. А тут получается, что как только создается промис, он сразу уже уходит в макрозадачи, а уже от туда, переходит в микроазадачи.


          1. tishka85
            11.08.2022 11:56

            Ты говоришь, что они срабатывают в основном потоке. Ну тогда, это значит, что после их срабатывания, они должны будут также пойти в конец. 

            нуу, да) так а что смущает? Если ты имел в виду первый проход по коду и в конец очереди макрозадач, то тут так всё и происходит. Они последовательно и идут в конец очереди макрозадач. Если говорить про проход по макрозадачам и переход в конец очереди микрозадач, то тут также всё логично и прозрачно. Они по одной (в последовательности исходя из задержки в setTimeout) переходят в конец очереди микрозадач (так как очередь пустая, то все они оказываются там всё время единственной задачей) и тут же выполняются.

            Кстати, сейчас вроде в статье всё описал как надо)


            1. jakut_bmstu Автор
              11.08.2022 12:27

              вот смотри. все таки я понимаю, что есть у нас недопонимание.

              у нас в основном потоке получается все это "промисы";
              P1, 1000
              P2, 2000
              P3, 1000
              P, 5000

              а в списке макрозадач
              ST1, 1000
              ST2, 100
              ST3, 2000
              ST4,1000

              И если говорить, что основной поток последовательно пойдет в конец макрозадач, то мы получим

              ST1, 1000
              ST2, 100
              ST3, 2000
              ST4,1000
              P1, 1000
              P2, 2000
              P3, 1000
              P, 5000

              отсортируем по времени задержки

              ST2,100
              ST1,1000
              ST4,1000 ___неверно! (он должен идти после P3)
              P1,1000
              P3,1000
              ST3,2000
              P2,2000
              P,5000

              а вот если мы их сразу будем регистрировать как макрозадачи, то тогда все ок

              ST1,1000
              P1,1000
              ST2,100
              P2,2000
              ST3,2000
              P3,1000
              ST4, 1000
              P,5000

              сортируем:

              ST2
              ST1
              P1
              P3
              ST4
              P2
              ST3
              P



              1. tishka85
                11.08.2022 13:00

                а в списке макрозадач
                ST1, 1000
                ST2, 100
                ST3, 2000
                ST4,1000

                И если говорить, что основной поток последовательно пойдет в конец макрозадач, то мы получим

                ST1, 1000
                ST2, 100
                ST3, 2000
                ST4,1000
                P1, 1000
                P2, 2000
                P3, 1000
                P, 5000

                почему?) ты же последовательно идешь по коду и последовательно добавляешь таски в список макрозадач. Почему у тебя вдруг сначала идут все ST, и потом только все P?

                1. Добавили в список макрозадач ST1

                2. Добавили в список макрозадач P1

                3. Добавили в список макрозадач ST2

                4. Добавили в список макрозадач P2

                5. Добавили в список макрозадач ST3

                6. Добавили в список макрозадач P3

                7. Добавили в список макрозадач ST4

                8. Добавили в список макрозадач P

                Затем идем по этому списку макрозадач в последовательности, которая регулируется параметром delay всех setTimeout в этой очереди


                1. jakut_bmstu Автор
                  11.08.2022 13:10

                  ну да, мой посыл был в том, что мы проходя по коду, их сразу кладем в макрозадачи.

                  а вот если мы их сразу будем регистрировать как макрозадачи, то тогда все ок

                  ST1,1000
                  P1,1000
                  ST2,100
                  P2,2000
                  ST3,2000
                  P3,1000
                  ST4, 1000
                  P,5000

                  сортируем:

                  ST2
                  ST1
                  P1
                  P3
                  ST4
                  P2
                  ST3
                  P



                  Просто формулировка основной поток, она подразумевает сначала выполнение всех задач в основном потоке, потом уже микрозадачи, потом макрозадача, и повторяем. я понял, что вот этот момент что-то не улавливаю до конца. по факту, если у нас код типо такого,
                  console.log(1)
                  setTimeout(()=>console.log(2),0)
                  console.log(3)

                  То мы согласны, получим 1 3 2
                  Тк сначала выполнится основной поток, потом макрозадача


                  1. Darkspice
                    11.08.2022 23:55

                    Добрый день. Опечатка небольшая в тексте под 3 задачей:

                    new Promise(resolve => setTimeout(resolve)).then(()=>console.log(4))

                    "Тут все ясно понятно. А вот что с четверкой? Микрозадача, порождает макрозадачу, обновим нашу табличку:"
                    Наверное имелось в виду - "Макрозадача (выполнение колэбэка setTimeout) порождает микрозадачу"

                    "Получается в момент выполнения микротасок, мы регистрируем макрозадачу, которая потом выполнит микрозадачу. Идем дальше."
                    Наверное имелось в виду "в момент выполнения executor'а регистрируется макротаска, при выполнении которой регистрируется микротаска"

                    По поводу ваших комментариев. Вы забываете что сначала setTimeout регистрируется в web api, а потом уже его коллбэк попадает в макротаску.

                    Вот упрощенная версия того, что происходит в этой задаче.


                    1. jakut_bmstu Автор
                      11.08.2022 23:55

                      а что за сервис, позволяющий так посмотреть? или что-то самописное?


                      1. Darkspice
                        12.08.2022 00:15

                        Это я просто гифку сделал.


                    1. jakut_bmstu Автор
                      11.08.2022 23:59

                      Касаемо третьей задачи, я согласен, выразился я не совсем ясно, и поэтому выглядит как опечатка или ошибка. Спасибо, исправлю.

                      "в момент выполнения executor'а регистрируется макротаска, при выполнении которой регистрируется микротаска"

                      именно это я и пытался сказать.