Асинхронность Event loop
Асинхронность
В JavaScript асинхронность — основной инструмент, который обрабатывает запросы параллельно с загрузкой веб-страницы. Сейчас невозможно представить интернет, где все запросы на сервер отправлялись бы с перезагрузкой страницы.
Любые данные от сервера запрашиваются асинхронно: отправляется запрос (XMLHttpRequest или XHR), и код не ждёт его возвращения, продолжая выполняться. Когда же сервер отвечает, объект XHR получает уведомление об этом и запускает функцию⚙️ обратного вызова — callback
, который передали в него перед отправкой запроса.
Если правильно использовать инструменты языка , то выполнение запроса, который происходит последовательно и в одном потоке, никак не мешает приёму событий и реакции на них — человек спокойно работает с интерфейсом, не замечая лагов, сбоев и зависаний.
Event loop
Event loop
в JavaScript — менеджер асинхронных вызовов.
Чтобы этот хитрый процесс слаженно работал, в JavaScript реализован механизм для управления очерёдностью исполнения кода . Поскольку это однопоточный язык , возникла необходимость "вклиниваться" в текущий контекст исполнения. Этот механизм называется event loop
— событийный цикл.
С английского loop
переводится как "петля", что отлично отражает смысл: мы имеем дело с закольцованной очередью.
Event loop
регулирует последовательность исполнения контекстов — стек. Он формируется, когда сработало событие или была вызвана функция⚙️. Реакция на событие помещается в очередь исполнения, в event loop
, который последовательно, с каждым циклом выполняет попадающий в него код . При этом привязанная к событию функция⚙️ вызывается следующей после текущего контекста исполнения.
В JavaScript постоянно работают связанные между собой синхронная и асинхронная очереди выполнения. Синхронная — stack
— формирует очередь и пробрасывает в асинхронную — event loop
— вызовы функций⚙️, которые будут выполнены после текущего запланированного исполняемого контекста.
Чтобы данные находились в консистентном состоянии, каждая функция⚙️ должна быть выполнена до конца. Это обусловлено однопоточностью JavaScript и некоторыми другими особенностями, например характерными для функциональных ⚙️языков программирования замыканиями. Поэтому единственный поток представлен в виде очереди контекстов исполнения, в которой и происходит "вклинивание" функций⚙️, прошедших через цикл событий.
Описание
JavaScript это однопоточный язык: одновременно может выполняться только одна задача. Обычно в этом нет ничего сложного, но теперь представьте, что вы запускаете задачу, которая занимает 30 секунд... Да. Во время этой задачи мы ждем 30 секунд, прежде чем что-либо еще может произойти (по умолчанию JavaScript запускается в главном потоке браузера, поэтому весь пользовательский интерфейс будет ждать) Сейчас 2021 год, никто не хочет медленный сайт который тупит.
К счастью, браузер предоставляет нам некоторые функции, которые сам механизм JavaScript не предоставляет: Web API. Который включает в себя DOM API, setTimeout, HTTP-запросы и так далее. Это может помочь нам создать асинхронное неблокирующее поведение .
Когда мы вызываем функцию, она добавляется в call stack(стек вызовов). Стек вызовов является частью механизма JS, это не зависит от браузера. Это классический взгляд на стек, т.е first in
, last out
. Когда функция возвращает значение, она "выталкивается" из стека.
function great() {
return 'Hello'
}
function respond() {
return setTimeout(() => alert('Hey!'), 1000)
}
great()
respond()
Функция respond
возвращает функцию setTimeout
. SetTimeout
предоставляется нам через Web-API
: он позволяет нам делить задачи, не блокируя основной поток. Callback
функция, которую мы передали в функцию setTimeout
, лямбда функция () => {return 'Hey'}
добавляется в Web-API
. Тем временем setTimeout
и responde
извлекаются из стека и возвращают свои значения.
В 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
, которая возвращает функцию setTimeout
.
Callback
который мы передали в setTimeout
добавляется в Web API
, функция setTimeout
и bar
извлекаются из стека вызовов.
Таймер запускается, тем временем foo
вызывается и записывает в журнал First
. foo
возвращает undefined
, baz
вызывается и callback
добавляется в очередь
baz
логирует Third
. Цикл обработки событий видит, что коллстек пуст после возврата baz
, после чего колбэк добавляется в стек вызовов.
Callback
логирует Second
.
Надеюсь, что это заставит вас чувствовать себя более уверено с циклом событий event loop
!
Не беспокойтесь, если это все еще кажется запутанным, самое важное - понять, откуда могут возникнуть определенные ошибки или специфическое поведение.
Проблемы?
Пишите в Telegram или ВКонтакте, а также подписывайтесь на наши новости
Вопросы:
Асинхронность - это:
- Инструмент, который выводит контекст исполнения функции из синхронного потока
- Инструмент, который исполняет код построчно
- Инструмент, который обрабатывает запросы параллельно с загрузкой веб-страниц
Менеджер асинхронных вызовов:
stack
Event loop
JavaScript
Вызовы функций помещаются в:
- Стек
- Кучу
- Петлю
Инструмент, выполняющий код с задержкой в миллисекундах:
delay
heap
setTimeout
Для того чтобы понять, на сколько вы усвоили этот урок, пройдите тест в мобильном приложении нашей школы по этой теме.
Ссылки:
- Объяснение работы EventLoop в JavaScript
- Как управлять event loop в JavaScript
- Справочник javascript
- Статья: Объяснение Event Loop в Javascript с помощью визуализации
- Статья: JavaScript Visualized: Promises & Async/Await
Contributors ✨
Thanks goes to these wonderful people (emoji key):
AlisaNasibullina | Dmitriy Vasilev 💵 | Resoner2005 🐛 🎨 |