Объяснение Event Loop в Javascript с помощью визуализации
Это перевод статьи Лидии Холли, где она объясняет с помощью визуализации как работает Event Loop в JS.
Event loop – это одна из тех вещей, с которыми так или иначе сталкивается каждый разработчик JavaScript, но поначалу немного сложно понять что к чему.
JavaScript это однопоточный язык: одновременно может выполняться только одна задача. Обычно в этом нет ничего сложного, но теперь представьте, что вы запускаете задачу, которая занимает 30 секунд... Да. Во время этой задачи мы ждем 30 секунд, прежде чем что-либо еще может произойти (по умолчанию JavaScript запускается в главном потоке браузера, поэтому весь пользовательский интерфейс будет ждать)😬 Это 2020 год, никто не хочет медленный, сайт который тупит.
К счастью, браузер предоставляет нам некоторые функции, которые сам механизм JavaScript не предоставляет: Web API. Который включает в себя DOM API, setTimeout, HTTP-запросы и так далее. Это может помочь нам создать асинхронное неблокирующее поведение 🚀.
Когда мы вызываем функцию, она добавляется в call stack(стек вызовов). Стек вызовов является частью механизма JS, это не зависит от браузера. Это классический взгляд на стек, т.е first in, last out. Когда функция возвращает значение, она "выталкивается" из стека.
Функция response возвращает функцию setTimeout. SetTimeout
предоставляется нам через веб-API: он позволяет нам делеить задачи, не блокируя основной поток. Callback функция, которую мы передали в функцию setTimeout
, лямбда функция () => {return 'Hey'}
добавляется в веб-API. Тем временем функция setTimeout
и функция response
извлекаются из стека, они оба возвращают свои значения.
В Web API таймер работает до тех пор, пока второй аргумент, который мы передали ему, не подождет 1000 мс. Callback не сразу добавляется в стек вызовов, а передается в нечто, называемое очередью.
Это может сбивать с толку: это не означает, что callback функия добавляется в стек вызовов (таким образом, возвращает значение) через 1000 мс! Он просто добавляется в очередь через 1000 мс. Но в этой очереди, функция должна ждать пока придет ее черёд.
Теперь это та часть, которую мы все ждали... Время для event loop выполнить единственную задачу: соединить очередь со стеком вызовов! Если стек вызовов пуст, то есть, если все ранее вызванные функции вернули свои значения и были извлечены из стека, первый элемент в очереди добавляется в стек вызовов. В этом случае никакие другие функции не были вызваны, что означает, что стек вызовов был пуст к тому времени, когда callback функция была первым элементом в очереди.
callback добавляется в стек вызовов, вызывается и возвращает значение, а также извлекается из стека.
Чтение статьи - это весело, но вы сможете полностью понять тему, не работая с ней снова и снова. Попробуйте выяснить, что появится в консоли, если мы запустим следующее:
const foo = () => console.log("First");
const bar = () => setTimeout(() => console.log("Second"), 500);
const baz = () => console.log("Third");
bar();
foo();
baz();
Давайте посмотрим, что происходит, когда мы запускаем этот код в браузере:
- Мы вызываем
bar
.bar
возвращает функциюsetTimeout
. - Callback который мы передали в
setTimeout
добавляется в Web API, функцияsetTimeout
иbar
извлекаются из стека вызовов. - Таймер запускается, тем временем
foo
вызывается и записывает в журналFirst
.foo
возвращает (undefined),baz
вызыввается и callback добавляется в очередь baz
логиркнтThird
. Цикл обработки событий видит, что коллстек пуст после возвратаbaz
, после чего колбэк добавляется в стек вызовов.- Callback логирует
Second
.
Надеюсь, что это заставит вас чувствовать себя более уверено с циклом событий (event loop)! Не беспокойтесь, если это все еще кажется запутанным, самое важное - понять, откуда могут возникнуть определенные ошибки или специфическое поведение.