HTML / CSSJavaScriptNode jsПаттерны проектированияПрактические

Event Loop

Событийно-ориентированное программирование (Event-Driven Programming) это когда выполнение кода детерминируется событиями

Основой Event-Driven Programming является Event Loop, регулирующий поток событий

При запуске нового скрипта движок JS:

  1. принимает содержимое входного файла
  2. оборачивает это в функцию
  3. устанавливает эту функцию обработчиком события «запуск» программы
  4. инициализирует другие переменные и функции
  5. эмитирует событие запуска программы
  6. добавляет это событие в очередь событий
  7. извлекает это событие из очереди и выполняет зарегистрированный обработчик, и, наконец!
  8. наша программа работает!

Поток выполнения (thread)

Поток — это последовательность команд (операторов языка, вызовов функций и т.д.), которые выполняются последовательно друг за другом Несколько потоков могут существовать в рамках одного и того же процесса и совместно использовать ресурсы (память)

Процесс — это экземпляр исполняемой программы, которому выделены системные ресурсы (время процессора и память)

  • Каждый процесс выполняется в отдельном адресном пространстве
  • Один процесс не может получить доступ к данным другого процесса

Каждая программа создает по меньшей мере один основной поток, который запускает функцию main()

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

Многопоточные языки используют несколько потоков.

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

❗️Важно
  • Ничто не исполнится, не попав в Call Stack
  • Все, что исполняется, пришло из Task Queue
  • Все, что находится в Task Queue, называется task

Стек и куча (heap)

Стек — это "быстрый" кусок оперативной памяти

Стек создаётся для каждого потока в многопоточных языках JS однопоточный язык, поэтому у нас только один стек (Call Stack) Стек организован по принципу LIFO (первым пришел — последним ушел).

  • Размер стека ограничен
  • Он задаётся при создании потока
  • Переменные, находящиеся в стеке, всегда являются локальными (приватными)

Heap ("куча") — это оперативная память, где хранятся глобальные переменные

"Куча" допускает динамическое выделение памяти Доступ к данным, хранящимся в "куче", обеспечивается посредством ссылок — переменных, значения которых являются адресами других переменных Поэтому "куча" работает медленнее, чем стек Процессор не контролирует "кучу" (в отличие от стека), поэтому для освобождения памяти "кучи" от ненужных переменных требуются "сборщики мусора"

Асинхронность

❗️Важно

JS — однопоточный язык.

Однако практически все движки — многопоточные

Неблокирующее поведение JS обеспечивается движком с помощью механизма Event Loop

Event Loop - это бесконечный цикл выполнения задач движком Это событийно-ориентированная модель Она держится на колбэках - функциях обратного вызова Каждый колбэк связан с определенным событием

В браузере есть API, обеспечивающие • работу таймеров (setTimeout, setInterval) • выполнение операций AJAX • отслеживание событий UI

Все это происходит в параллельных потоках

Когда истекает временной интервал таймера, или завершается операция AJAX, или происходит событие UI, вызов соответствующего колбэка помещается в очередь задач В стек вызовов (Call Stack) основного потока он может попасть только отсюда, и только тогда, когда стек вызовов будет пуст, т.е. все текущие вызовы основного потока завершатся

event loop

Итак, движок имеет очередь задач (Message Queue) Если событий не происходит, эта очередь пуста. В момент, когда происходит событие, его обработчик помещается в конец очереди

Microtask

Мы уже поняли, что установка таймеров и обработчиков событий UI - это создание тасков

Однако не все задачи равны "по рангу"

Есть макрозадачи и микрозадачи

У них разные очереди, т.е. они никогда не смешиваются

На каждом "витке" Event Loop выполняется одна макрозадача из очереди

После завершения очередной макрозадачи Event Loop "принимается" за очередь микрозадач

Следующий таск из очереди макрозадач не начнет выполняться до тех пор, пока не будут выполнены все микротаски из очереди микрозадач

🔥Важно

Перерисовка (рендер) страницы браузером так же не будет выполняться до тех пор, пока не будет опустошена очередь микрозадач

К числу микрозадач относятся колбэки промисов и MutationObserver

microtask

Примеры

:one:

В этом примере таск, который запускается таймером, "подвешивает" работу браузера

Обработчики события click ждут в очереди задач завершения работы таска, запущенного таймером

Если браузер не успел выполнить перерисовку страницы с сообщением "Start" до того, как запустился таск таймера, то мы увидим это сообщение на странице после того, как длинный цикл в таймере завершится...

1function message ( text ) {
2 document.body.innerText += `${text}\n\n`
3}
4
5document.body.onclick = (
6 () => {
7 let counter = 0
8 return event => message ( `body clicked ${++counter} times` )
9 }
10)()
11
12setTimeout (
13 function () {
14 for ( var x=0; x<10000000000; x++ ) continue
15 message ( "Counting finished" )
16 },
17 0
18)
19
20message ( "Start" )

Promise

Колбэк, передаваемый методу then() промиса, является microtask

❗️Важно

Отличие microtask от task (task === macrotask) заключается в том, что браузер "впихнет" его между тасками без очереди. Иными словами `task (macrotask) из очереди не будет запущен до тех пор, пока не опустошится очередь microtask-ов.

Как только мы запускаем скрипт на исполнение, мы стартуем task В примере ниже это вызов функции message, установка таймеров и обработчика события click на document.body Кроме того, мы запускаем три асинхронных операции, используя Fetch API браузера Но мы знаем, что метод fetch() возвращает promise.

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

1function message ( text ) {
2 document.body.innerText += `${text}\n\n`
3}
4
5document.body.onclick = (
6 () => {
7 let counter = 0
8 return event => message ( `body clicked ${++counter} times` )
9 }
10)()
11
12message ( 'start' )
13
14setTimeout ( () => message ( 'timeout 0' ) , 3000 )
15
16fetch ( "https://api.github.com/users" )
17 .then ( response => response.json () )
18 .then ( users => message ( `1: ${users[0].login}` ) )
19
20setTimeout ( () => message ( 'timeout 1' ), 2000 )
21
22fetch ( "https://api.github.com/users?since=250" )
23 .then ( response => response.json () )
24 .then ( users => message ( `2: ${users[0].login}` ) )
25
26setTimeout ( () => message ( 'timeout 2' ), 100 )
27
28fetch ( "https://api.github.com/users?since=300" )
29 .then ( response => response.json () )
30 .then ( users => message ( `3: ${users[0].login}` ) )
31
32setTimeout ( () => message ( 'timeout 3' ), 0 )
33
34section.dispatchEvent ( new Event ( "click" ) )

Каждый клик будет порождать новый таск, который будет помещен в очередь задач Если ваш клик опередит завершение операции fetch, то соответствующее сообщение появится раньше, чем имена github юзеров Однако колбэки таймеров будут сдвинуты в очереди задач, как только завершится fetch-запрос к серверу

Как работает Event Loop

Original source

Практика 👩‍💻👨‍💻

Задание 1

Используя асинхронную функцию, написать код функции printString, выводящей строку на страницу по 1 символу в секунду. Используйте prompt для того что бы указать строку.

1function printString(str){
2}

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

1let wrapper = container && container.nodeType === 1 ?
2 container : document.body.appendChild (
3 document.createElement ( "div" )
4 )

Для добавления символов в элемент container используйте методы appendChild и document.createTextNode

1wrapper.appendChild (
2 document.createTextNode(
3 // write code here
4 )
5)
Hello