Comments 26
Полезный текст для прохождения собеседований.
Но, честно говоря, не хочется тащить в прод такой код, где прям нужны такие знания или идет опора в определении последовательность операций именно на то как работает event loop
Скорее всего он уже в проде внутри реакта/ангуляра/вью
Эти знания нужны чаще в момент отладки, чтоб можно было объяснить себе и всем заинтересованным почему оно работает именно так. Часто бывает что мы что-то грузим в 3-5 местах и вдруг что-то загружается раньше времени и вот тут как раз понимание ивент лупа очень кстати.
1746 это некий случайный идентификатор. Он к нашему ответу отношения не имеет.
Если кто может более точно объяснить почему он тут появляется, прошу в комментарии.
Когда что-то выполняешь вручную в консоли, оно пишет результат последней синхронной операции. Здесь последним был setTimeout, который вернул id созданного таймаута (тот самый, на который можно натравить clearTimeout). Да, это просто число.
В продакшене я бы не стал на такое полагаться. Даже на то что два сетТаймаута с одинаковой задержкой выполнятся в порядке их создания — не надо полагаться.
На собеседовании хорошо при таких вопросах отстоять позицию того что есть сейчас, а есть «потом» и это «потом» будет синхронизировано через более надёжные механизмы. Если работодатель настаивает, то можно выполнить код в консоли. Если хочет решения на бумажке, то встать и уйти, нервы дороже.
Интересно освежить в памяти.
В задаче 3 название колонок 2 и 3 перепутаны.
1746 - айди от setTimeout, который можно использовать в clearTimeout, дабы удалить таймер
вообще интересная статья
если интересно, то можешь еще добавить requestAnimationFrame
и + есть подвохи в триггерных кликах https://jsfiddle.net/e5fqrapb/
В 4 задаче не сходится. Все setTimeout в функции myPromise будут идти и регистрироваться синхронно, в потоке основного кода. Соответственно всё что вы занесли в микрозадачи должны по идее быть в макрозадачах. Затем, когда финально собираем ответ:
Срабатывает in 'setTimeout2' через 100 мс
'in setTimeout1' через 1000
Через 1000 мс резолвится первый промис (запускается колбек, который добавляет в очередь микротасок колбек с console.log('in Promise 1'))
Далее цикл событий видит, что в очереди микротасок появилось задание, он тут же его выполняет (console.log('in Promise 1'))
Очередь микротасок пуста, следовательно возвращаемся к макротаскам => Через 1000 мс резолвится второй промис (запускается колбек, который добавляет в очередь микротасок колбек с console.log('in Promise 3'))
Далее цикл событий видит, что в очереди микротасок появилось задание, он тут же его выполняет (console.log('in Promise 3'))
и т.д.
не совсем понял, что ты имеешь ввиду под словом "не сходится". Порядок выполнения вроде верный.
сейчас попробую объяснить. Просто такое ощущение, что ты не до конца разобрался с этой задачей и в итоге не совсем правильно объяснил последовательность и механику, каким образом этот код будет выполняться (проще говоря "подогнал" ответ под решение). Соответственно, новички, читающие статью, могут либо запутаться в этом примере либо вообще не понять в итоге, как там всё происходит.
Смотри,
По факту, у нас выполнятся все микрозадачи, и они займут свое место в очереди макрозадач
тебя не смутило, что после этой фразы микрозадачи не добавляются по одной в конец очереди макрозадач (как это должно быть и как это было в задаче 2 с console.log(4), ты там сам писал, что она идет в конец очереди макрозадач и это правильно, тут не поспоришь), вместо этого они как бы перемешиваются с уже существующими в очереди макрозадачами, передвигаясь просто вправо.
Ответ прост: ИЗНАЧАЛЬНО все задачи в очереди микрозадач не должны быть в колонке с микрозадачами. Они должны идти в колонке с макрозадачами, потому что как я уже писал:
Все setTimeout в функции myPromise будут идти и регистрироваться синхронно, в потоке основного кода
И только потом, при разборе очереди макрозадач по одной на небольшой промежуток времени эти задачи попадают в список микрозадач (так как резолвится этот промис) и тут же выполняются (так как после каждой макротаски идет выполнение микротасок - при наличии их в очереди). Как раз это я показал в первом комментарии, в своей последовательности (список действий не полный, так как потом происходят аналогичные действия, смысла нет повторять)
Соглашусь, надо поправить, почему-то действительно биполярочка. Спасибо, что подметил!
Поправил. Однако знаешь, я вот не до конца понял момент. Ты говоришь, что они срабатывают в основном потоке. Ну тогда, это значит, что после их срабатывания, они должны будут также пойти в конец. А тут получается, что как только создается промис, он сразу уже уходит в макрозадачи, а уже от туда, переходит в микроазадачи.
Ты говоришь, что они срабатывают в основном потоке. Ну тогда, это значит, что после их срабатывания, они должны будут также пойти в конец.
нуу, да) так а что смущает? Если ты имел в виду первый проход по коду и в конец очереди макрозадач, то тут так всё и происходит. Они последовательно и идут в конец очереди макрозадач. Если говорить про проход по макрозадачам и переход в конец очереди микрозадач, то тут также всё логично и прозрачно. Они по одной (в последовательности исходя из задержки в setTimeout) переходят в конец очереди микрозадач (так как очередь пустая, то все они оказываются там всё время единственной задачей) и тут же выполняются.
Кстати, сейчас вроде в статье всё описал как надо)
вот смотри. все таки я понимаю, что есть у нас недопонимание.
у нас в основном потоке получается все это "промисы";
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
а в списке макрозадач
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?
Добавили в список макрозадач ST1
Добавили в список макрозадач P1
Добавили в список макрозадач ST2
Добавили в список макрозадач P2
Добавили в список макрозадач ST3
Добавили в список макрозадач P3
Добавили в список макрозадач ST4
Добавили в список макрозадач P
Затем идем по этому списку макрозадач в последовательности, которая регулируется параметром delay всех setTimeout в этой очереди
ну да, мой посыл был в том, что мы проходя по коду, их сразу кладем в макрозадачи.
а вот если мы их сразу будем регистрировать как макрозадачи, то тогда все ок
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
Тк сначала выполнится основной поток, потом макрозадача
Добрый день. Опечатка небольшая в тексте под 3 задачей:
new Promise(resolve => setTimeout(resolve)).then(()=>console.log(4))
"Тут все ясно понятно. А вот что с четверкой? Микрозадача, порождает макрозадачу, обновим нашу табличку:"
Наверное имелось в виду - "Макрозадача (выполнение колэбэка setTimeout) порождает микрозадачу"
"Получается в момент выполнения микротасок, мы регистрируем макрозадачу, которая потом выполнит микрозадачу. Идем дальше."
Наверное имелось в виду "в момент выполнения executor'а регистрируется макротаска, при выполнении которой регистрируется микротаска"
По поводу ваших комментариев. Вы забываете что сначала setTimeout регистрируется в web api, а потом уже его коллбэк попадает в макротаску.
Вот упрощенная версия того, что происходит в этой задаче.

а что за сервис, позволяющий так посмотреть? или что-то самописное?
Касаемо третьей задачи, я согласен, выразился я не совсем ясно, и поэтому выглядит как опечатка или ошибка. Спасибо, исправлю.
"в момент выполнения executor'а регистрируется макротаска, при выполнении которой регистрируется микротаска"
именно это я и пытался сказать.
Спасибо, хорошая статья
Мне понравилась статья. Информативная.
Можно ещё дополнить серией статей от Лидии Хейли с визуализицией основных концепций в JavaScript. Первая как раз про Event Loop.
https://dev.to/lydiahallie/javascript-visualized-the-javascript-engine-4cdf
Есть два вида JS: для работы и для интервью.
Привет! На одном из собеседований была вот такая задача:
function log(x) {
return () => console.log(x);
}
document.addEventListener('myevent', log('A'));
document.dispatchEvent(new CustomEvent('myevent'));
setTimeout(log('B'), 0);
setTimeout(log('C'), 16);
requestAnimationFrame(log('D'));
Promise.resolve().then(log('E'));
window.addEventListener('message', log('F'));
window.postMessage({}, location.origin);
Тот случай, когда сложнее вспомнить, что является макро-задачей, что микро, а что ни тем ни другим
Имхо, это грязь. Чуваки не смогли придумать нормальный челлендж, и придумали этот.
Во первых, синхронная обработка события dispatchEvent - малоизвестный факт, которым почти не пользуются, кроме, может быть, каких-то исключительных кейсов (хотя да, в учебнике об этом есть). Во вторых, буквы B, D и F могут выскочить в разной последовательности, в зависимости от браузера и обстоятельств.
Задачи на собеседованиях. Event loop. JS