Pull to refresh

Comments 13

Большое спасибо за статью. Никогда не помешает освежить знания. Спасибо!

Из статьи не понятно, на каждую область видимости создаётся свой Event Loop или же существует только один глобальный Event Loop? Сможете раскрыть этот момент подробнее?

Один глобальный Event Loop обрабатывает все асинхронные операции независимо от областей видимости.

Я читал много разных статей и смотрел много разных видео на youtube про JS event-loop, но ваши 20 строк кода с while(true) – пожалуй, самое доходчивое и лаконичное объяснение, которое только можно придумать. Спасибо!

В самом начале
console.log(anotherAcc, acc); // 3 3
не 3 3, а 2 2

new Promise((resolve) => { console.log('Step 3: In promise constructor'); }).then(() => console.log('Step 4: In then'));

В некоторых листингах в конструкторе обещания забыли вызвать resolve().

Ну и на мой взгляд хорошо бы явно объяснить что такое:

  • callBack stack. И что он работает по стэку.

  • macroTasks, microTasks; и что они работают по очереди.

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

Так как не раскрыта эта часть, то встречаются предложения которые могут ввести в заблуждение, например:

Event Loop является стеком, где хранятся все задачи, которые не вошли в синхронный поток выполнения.

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

А так статья отличная!

отличная статья! Очень информативно и понятно

Есть конечно некоторые неточности, например понятие макротаски (не существует в рамках спецификации такого названия), но это абсолютно не критично.

асинхронности в самом JavaScript как таковой нет

JS это язык, который выполняется в разных средах. Думаю с этого и стоило начать, о каком именно EventLoop пойдет речь.

Дело в том, что Event Loop - единственный механизм в JavaScript, который позволяет реализовать асинхронность (хотя по сути все операции выполняются синхронно, просто очень быстро, об этом далее)

Как очень быстро выполняется "синхроно" (асинхронно естественно) чтение файла в nodejs?

Не хватает статьи, где бы доходчиво объяснили миру для чего были вообще созданы макро-, и особенно, микро-задачи, здесь автор вполне неплохим примером "реализации" механизма event loop'а показал лишь порядок прогона callback'ов в этом "бесконечном" событийном цикле

Все пояснения материала, относительно того, как "что-то работает" не имеют ничего общего с реальностью и представляют из себя винегрет из существующих мифов и фантазии автора. (комментарием ниже я разбираю все детали єтого)

Касательно же вопроса о том откуда взялись макро и микротаски нужно сказать слюдующее:

Термин Task queue и microtask queue принадлежат спецификации html5 и не имеют никакого отношения к javascript. Однако Вы правы в том, что заподозрили связь между термином и существующей тому причиной.

Официальная спецификация языка JS седержала раньше две очереди: generic queue и promise queue которые никакого отношения к евент луп не имеют, но при єтом накладывают некоторые огранечения на то, как хост среда может выполнять тот или оной код. Авторы стандарта html5 просто зеркально отобразили єти очереди в свой стандарт, дополнив их необходимым им функционалом.

Важно понимать то, что в js нет и не может быть ничего подобного евент луп. То есть оригинальные очереди решают вовсем иные задачи нежели те, которые приписываются event loop

Представленный материал не имеет ничего общего с официальной спецификацией языка ECMAScript. И представляет из себя как пересказ современных мифов о языке, так и, ничем не подтверждаемые, персональные представления автора.

То есть представления, которые прямо противоречат официальной спецификации.

Судите сами:

Тем не менее у JavaScript есть такой механизм как Event Loop, который как раз и позволяет выполнять "асинхронные" операции.

JavaScript не имел и не имеет такого механизма как EventLoop. EventLoop это специфическая для HOST среды особенность, которая, в том числе, может выполнять JavaScript код.  

Например поведение EventLoop в HOST среде реализующей стандарт HTML5 описано именно в стандарте HTML5.  Одновременно с этим, поведение схожего механизма в Node - описано в документации для libuv

При этом поведение в первой и второй HOST среде обладает достаточными различиями, чтобы утверждать - не может быть единого описания работы EventLoop даже в случае, если мы упрощаем задачу вычеркивая несущественные особенности реализации.

Да просто потому что JavaScript тоже выполняет их синхронно, асинхронности в самом JavaScript как таковой нет. 

Всем свидетелям секты однопоточного JavaScript, крайне рекомендую к прочтению современную спецификацию. Например вот  с этой главы официальной спецификации

Если заявлений официальной спецификации недостаточно, то можно почитать, что об этом думают авторы спецификации HTML5

После чего, либо изменить свою точку зрения, либо оставаться адептами одно-поточного JavaScript-а.

Для тех, кто обескуражен дам краткое ИГОГО:

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

  2. Под термином поток, следует понимать не thread в его академическом смысле, но возможность организации выполнения более одной единицы кода в одно и тоже время.

  3. Для реализации заявленного, в том числе, в официальной спецификации реализован Atomics Object регламентирующий все типичные проблемы возникающие при доступе к одному и тому же сегменту памяти разными потоками. Вплоть до создания отдельной очереди обрабатывающей таймауты для операций, которые пытаются прочитать, заблокированную другим потоком, область памяти. 

  4. В современной спецификации не существует реализации возможности самостоятельного запуска параллельного выполнения JS кода. Однако существует все необходимое, чтобы эту возможность через свое API предоставила HOST среда. Что и сделало возможным в свое время появление таких вещей как Worker-ы.

С синхронным кодом все более или менее ясно: интерпретатор проходится по каждой инструкции выполняет ее и все работает.

Заявленное не имеет ничего общего с тем как современная спецификация регламентирует работу JS кода

С асинхронным кодом все немножко сложнее. Рассмотрим следующий пример: [...]

Как мы можем увидеть console.log, который был в setTimeout почему-то выполнился позже, но почему так случилось?

Тут в силу и вступает Event Loop. Так как setTimeout это асинхронная операция (таймер высчитывается на стороне браузера, а не в JS).

console.log, setTimeout - это API предоставляемые HOST средой. Никакого отношения к тому, как выполняется JS код они не имеют. Ровно как не имеют отношения к тому, как выполняется асинхронный код в языке JavaScript. 

Регламентируют работу этих API сама HOST среда. И только она решает как и когда будет что-то выполнено. В рамках HOST среды типичного браузера, то есть среды реализующей стандарт HMTL5 - регламентом работы для этого API служит спецификация HTML5. Где и раскрыты мистические шаги описывающие поведение setTimeout.

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

setTimeout это внешнее API. ВСЕ что с ним связано регламентируется HOST средой которая это API реализует. Никакого отношения к этому JS не имеет. Операция никуда не пропадает даже под соусом как бы.

  

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

Это полнейшая чушь не только с точки зрения спецификации JavaScript, но и с точки зрения стандарта HTML5 - появление в любой из очередей ссылки на задачу для выполнения какого-либо кода, происходит только тогда, когда этот код УЖЕ нужно выполнять. Иными словами, таймер или коллбэк  не существует в EventLoop до тех пор пока тот самый таймер не истечет.

Где происходит истечение этого таймера? В недрах HOST среды которая обслуживает это API. 

Event Loop будет держать все что в нем содержится, покуда у нас не очистится Call Stack. Только после того как все синхронные операции в функции выполнились Event Loop отдаст нам наш таймер, который мы сможем выполнить

Это полнейшая чушь. Event Loop стандарта HTML5 оперирует термином Task. Task внутри себя может содержать что угодно - от одной команды до громадного блока команд.  Например весь код callback функции - это один task. 

Как уже было сказано выше интерпретатор в JavaScript выполняет одну операцию за раз, все что является асинхронным он отправляет в Event Loop.

Это фантазии автора, которые если и отвечают реальности - то только в одном частном случае. 

Однако, вы наверное могли слышать о таких вещах как "макротаски" и "микротаски".

В рамках стандарта HTML5, EventLoop содержит в себе две основные дефиниции: это  task queue и micro task queue. Никаких макротасков и прочих тасков НЕТ. 

  

Дело в том, что Event Loop - единственный механизм в JavaScript, который позволяет реализовать асинхронность

В языке JavaScript НЕТ EventLoop. Решать задачи выполнения асинхронного кода может ТОЛЬКО HOST среда. Потому, что язык JavaScript это скриптовый язык - у которого НЕ МОЖЕТ БЫТЬ своего ввода вывода. Как следствие, язык JavaScript НЕ МОЖЕТ иметь своего API для обслуживания событий или создания тредов. Это задача HOST среды. 

Event Loop является стеком, где хранятся все задачи, которые не вошли в синхронный поток выполнения.

Нет никакого синхронного потока выполнения. 

  

В микрозадачи попадают в основном только две категории: then у промисов, а также Intersection Observer.

Microtask queue это калька с механизма ECMAScript спецификации HostEnqueuePromiseJob. Который создан таковым потому, что авторы спецификации Promise настаивали на строгом соблюдении порядка выполнения задач связанных с разрешением промисов. 

Intersection Observer - это API стандарта HTML5 которое попало в microtask queue стандарта HTML5 по тем же причинам - авторам API было чрезвычайно важно чтобы порядок выполнения задач связанных с API был строго детерминирован. И чтобы не городить лишних огородов его просто добавили туда же.

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

В рамках этой истории, крайне важно иметь гарантии, что те задачи для которых порядок выполнения операций КРИТИЧЕСКИ важен - получат этот порядок. 

Современное поведение браузера как HOST среды, по умолчанию создает отдельный Агент для каждой вкладки и как следствие отдельный Event Loop. Что продиктовано было причинами безопасности связанными с появившимся уязвимостями типа Spectre и Meltdown и схожие с ними, которых сейчас насчитывается уже с десяток.

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

Если бы мы делали свой Event Loop, то он бы выглядел следующим образом:

Эта реализация не имеет ничего общего со стандартом. Например:

for (const task of eventLoop.microtasks) {

    task.execute();

  }

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

Начало выполнения очереди microtask queue происходит ТОЛЬКО после выполнения Task из Task queue. Шаг 2.7.

Самое интересное в данном коде находится внизу, внутри цикла while

В этом коде нет ничего интересного потому, что он показывает что угодно, но только не то, как работает Event Loop стандарта HTML5. И при этом не имеет вообще ничего общего с Event Loop из LibUv.

Все дальнейшее комментировать нет уже никаких сил.

Вместо ИГОГО

Использовать этот материал в образовательных целях крайне не рекомендуется, поскольку он отражает исключительно представление автора о том как это работает, одновременно вступая в прямое противоречие с официальной спецификацией регламентирующей как функционирование языка ECMAScript так и описывающей поведение EventLoop как часть стандарта HTML5.

Sign up to leave a comment.

Articles