✨♻️ Визуализируя JavaScript. Часть 2/7: цикл событий [перевод]
Tags: javascript, webdev, learning, frontend
Published: 2023-12-06 16:35:28

Данный текст является переводом оригинальной статьи **[✨♻️ JavaScript Visualized: Event Loop](https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif)** авторства ****[Lydia Hallie](https://dev.to/lydiahallie). В процессе адаптации на русский язык я попыталась сохранить стиль автора.

Статья является частью серии из 7-ми текстов, в которой Лидия доступно, с живыми иллюстрациями и юмором, раскрывает одни из самых базовых и, вместе с тем, сложных для понимания концепций и принципов JavaScript.

# Введение

*«О боже, цикл событий»* — так наверняка думал хотя бы раз в своей жизни любой frontend–разработчик. Это одна из тех вещей, с которыми так или иначе приходится сталкиваться каждому JavaScript разработчику чуть ли не каждый день, но поначалу концепция Event Loop кажется запутанной. Я визуал и умею наглядно представлять информацию, поэтому решила попытаться помочь вам, объяснив это визуально с помощью гифок с низким разрешением, потому что на дворе 2019 год [на момент написания оригинального поста], а гифки почему-то все еще пиксельные и размытые.

Но прежде всего, что такое цикл событий и почему вас это должно волновать?

JavaScript является **однопоточным** : одновременно может выполняться только одна задача. Обычно в этом нет ничего страшного, но теперь представьте, что вы выполняете задачу, которая занимает 30 секунд... М-да... Во время этой задачи мы ждем 30 секунд, прежде чем что-то еще может произойти (JavaScript по умолчанию запускается в основном потоке браузера, поэтому весь интерфейс завис) 😬 На дворе 2019 год, никому не нужен медленный и не отвечающий сайт.

К счастью, браузер предоставляет нам некоторые функции, которых нет в самом движке JavaScript: Web API. Сюда входят DOM API, `setTimeout`, HTTP-запросы и т. д. Это может помочь нам создать асинхронное, поведение, которое не будет блокировать основной поток 🚀

# Стек вызовов

Когда мы вызываем функцию, она добавляется в нечто, называемое стеком вызовов (call stack). Стек вызовов является частью движка JS и не зависит от браузера. Это очередь, что означает “первый пришёл, последний ушёл” (представьте себе стопку блинов). Когда функция возвращает значение, оно извлекается из стека 👋

> Стек вызовов — это структура данных, которая, говоря упрощённо, записывает сведения о месте в программе, где мы находимся. Если мы переходим в функцию, мы помещаем запись о ней в верхнюю часть стека. Когда мы из функции возвращаемся, мы вытаскиваем из стека самый верхний элемент и оказываемся там, откуда вызывали эту функцию. Это — всё, что умеет стек.
> 


![1 || Функции помещаются в стек вызовов, когда мы их вызываем, и извлекаются, когда возвращают какой-то результат](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/burh7kug5g07ibtyogjx.gif)

*1 || Функции помещаются в стек вызовов, когда мы их вызываем, и извлекаются, когда возвращают какой-то результат*

# Очередь вызовов и setTimeout

Функция `respond` возвращает `setTimeout` функцию. `setTimeout` предоставляется Web API: он позволяет нам откладывать задачи на заданное время, не блокируя основной поток. Функция обратного вызова (коллбэк), которую мы передали функции `setTimeout`, стрелочная функция `() => { return 'Hey'}`, добавляется в Web API. Тем временем `setTimeout` функция и функция `respond` извлекаются из стека, они обе вернули свои значения!


![2 || setTimeout предоставляется нам со стороны браузера, Web API позаботится о коллбэке, который мы передали](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ca3pgjj89ewbq012l1zh.gif)

*2 || setTimeout предоставляется нам со стороны браузера, Web API позаботится о коллбэке, который мы передали*

В Web API таймер работает столько, сколько мы указали во втором аргументе, — 1000 мс (по умолчанию время в JS указывается в мс). Коллбэк не добавляется сразу в стек вызовов, вместо этого он откладывается в нечто, называемое очередью.


![3 || Когда время таймера закончится (1000 мс, в данном случае), коллбэк будет помещён в очередь вызовов](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qz1dn6e31uede9lexocc.gif)

_3 || Когда время таймера закончится (1000 мс, в данном случае), коллбэк будет помещён в очередь вызовов_

Это может сбить с толку: окончание таймера не означает, что функция обратного вызова добавляется в стек вызовов (и, следовательно, возвращает значение) через 1000 мс! Коллбэк просто добавляется в *очередь* через 1000 мс. Но это очередь, функция должна дождаться своей очереди!

> Очереди работают по принципу “первый пришёл, первый вышел” (представьте себе очередь в магазине).
> 

Наконец, часть, которую мы все ждали… Пришло время циклу событий выполнить свою единственную задачу: **соединить очередь со стеком вызовов**! Если стек вызовов **пуст**, т. е. все ранее вызванные функции вернули свои значения и были извлечены из стека, *первый элемент* из очереди добавляется в стек вызовов. В этом случае никакие другие функции не вызывались, а это означает, что стек вызовов был пуст к тому моменту, когда функция обратного вызова стала первым элементом в очереди.

![4 || Цикл событий смотрит на очередь коллбэков и на стек вызовов. Есть стек вызовов пуст, в него перемещается первый элемент из очереди коллбэков](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zc1q8yf1hapxinsghxjg.gif)

_4 || Цикл событий смотрит на очередь коллбэков и на стек вызовов. Есть стек вызовов пуст, в него перемещается первый элемент из очереди коллбэков_

Наконец, коллбэк добавляется в стек вызовов, вызывается, возвращает значение и извлекается из стека. Ура! 🎉


![5 || Коллбэк добавляется в стек вызовов и выполняется. После того, как функция возвращает значение, они извлекается из стека вызовов](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g0t1d0ivuj5trbo8ip4k.gif)

_5 || Коллбэк добавляется в стек вызовов и выполняется. После того, как функция возвращает значение, они извлекается из стека вызовов_

Читать статью весело, но вам станет комфортно со стеком вызовов только тогда, когда будете работать с ним снова и снова. Попытайтесь выяснить, что выведется в консоль, если мы запустим следующей код:

```javascript
const foo = () => console.log("First");
const bar = () => setTimeout(() => console.log("Second"), 500);
const baz = () => console.log("Third");

bar();
foo();
baz();
```

Разобрались? Давайте быстро взглянем, что происходит, “за кулисами” когда мы запускаем этот код в браузере:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/if6tkd11i81x4b835q9s.gif)

1. Мы вызываем `bar`.`bar` возвращает таймер `setTimeout`.
2. Коллбэк, который мы передали в `setTimeout`, добавляется в Web API, функции `setTimeout` и `bar` извлекаются из стека вызовов.
3. Таймер запускается, тем временем `foo` вызывается и логирует `First`. `foo` возвращает `*undefined*`.
4. Вызывается `baz` и попадает в стек вызовов, тем временем, коллбэк таймера добавляется в очередь. 
5. `baz` логирует `Third`. Цикл событий видит, что стек вызовов пустеет после того, как из  `baz` возвращается `*undefined*`. Теперь коллбэк таймера добавляется в стек вызовов.
6. Наконец, логируется `Second`. Коллбэк отработал, вернул `*undefined*` и вышел из стека вызовов.

# Заключение

Надеюсь, что благодаря этому посту вы почувствуете себя более комфортно с циклом событий! Не волнуйтесь, если это все еще кажется запутанным, самое главное — **понять, откуда могут возникнуть определенные ошибки/поведение,** чтобы **эффективно находить в Google нужные термины** и, в конечном итоге, оказаться на правильной странице Stack Overflow 💪🏼 Не стесняйтесь обращаться к автору, если у вас есть вопросы!

---

---

**Lydia Hallie в соцсетях: [Twitter](https://www.twitter.com/lydiahallie) || [Instagram](https://www.instagram.com/theavocoder) || [GitHub](https://www.github.com/lydiahallie) || [LinkedIn](https://www.linkedin.com/in/lydia-hallie) || [Website](https://www.lydiahallie.io/)**

**P.S.** Автор использовала **Keynote** для создания анимаций и записей экрана.

**P.P.S.** В других статьях будут рассмотрены такие штуки, как *Поднятие* (*hoisting*), область действия (scope chain), прототипное наследование (prototypal inheritance), генераторы и итераторы (generators ans iterators), промисы (promises) и async/await.