-
Notifications
You must be signed in to change notification settings - Fork 16
event loop
Событийно-ориентированное программирование ( Event-Driven Programming ) — это когда выполнение кода детерминируется событиями
Основой Event-Driven Programming является Event Loop, регулирующий поток событий
При запуске нового скрипта движок JS:
1) принимает содержимое входного файла
2) оборачивает это в функцию
3) устанавливает эту функцию обработчиком события «запуск» программы
4) инициализирует другие переменные и функции
5) эмитирует событие запуска программы
6) добавляет это событие в очередь событий
7) извлекает это событие из очереди и выполняет зарегистрированный обработчик,
и, наконец! -
8) наша программа работает!
Asynchronous Programming in Javascript CSCI 5828:
Foundations of Software Engineering Lectures 18–10/20/2016
by Kenneth M. Anderson
⚠️ Ничто не исполнится, не попав в Call Stack
⚠️ Все, что исполняется, пришло из Task Queue
⚠️ Все, что находится в Task Queue, называется task
Поток — это последовательность команд ( операторов языка, вызовов функций и т.д. ), которые выполняются последовательно друг за другом
Несколько потоков могут существовать в рамках одного и того же процесса и совместно использовать ресурсы ( память )
Процесс — это экземпляр исполняемой программы, которому выделены системные ресурсы ( время процессора и память)
Каждый процесс выполняется в отдельном адресном пространстве
Один процесс не может получить доступ к данным другого процесса
Каждая программа создает по меньшей мере один основной поток, который запускает функцию main()
Программа, использующая только основной поток, является однопоточной
Многопоточные языки используют несколько потоков
При наличии только одного процессора ему приходится довольно часто переключаться с одного потока на другой, чтобы создать иллюзию одновременного выполнения кода во всех потоках
В многопроцессорных и многоядерных системах каждый процессор ( ядро ) обслуживает отдельный поток, поэтому потоки действительно выполняются параллельно ( одновременно )
Стек — это "быстрый" кусок оперативной памяти
Стек создаётся для каждого потока в многопоточных языках
JS однопоточный язык, поэтому у нас только один стек (Call Stack)
Стек организован по принципу LIFO ( первым пришел - последним ушел )
Размер стека ограничен
Он задаётся при создании потока
Переменные, находящиеся в стеке, всегда являются локальными ( приватными )
Heap ( "куча" ) — это оперативная память, где хранятся глобальные переменные
"Куча" допускает динамическое выделение памяти
Доступ к данным, хранящимся в "куче", обеспечивается посредством ссылок - переменных, значения которых являются адресами других переменных
Поэтому "куча" работает медленнее, чем стек
Процессор не контролирует "кучу" ( в отличие от стека ), поэтому для освобождения памяти "кучи" от ненужных переменных требуются "сборщики мусора"
JS — однопоточный язык
Однако практически все движки — многопоточные
Неблокирующее поведение JS обеспечивается движком с помощью механизма Event Loop
Event Loop - это бесконечный цикл выполнения задач движком |
|---|
Это событийно-ориентированная модель
Она держится на колбэках - функциях обратного вызова
Каждый колбэк связан с определенным событием
В браузере есть API, обеспечивающие
• работу таймеров (setTimeout,setInterval)
• выполнение операций AJAX
• отслеживание событий UI
Все это происходит в параллельных потоках
Когда истекает временной интервал таймера, или завершается операция AJAX, или происходит событие UI,
вызов соответствующего колбэка помещается в очередь задач
В стек вызовов (Call Stack) основного потока он может попасть только отсюда,
и только тогда, когда стек вызовов будет пуст,
т.е. все текущие вызовы основного потока завершатся
![]() |
|---|
Итак, движок имеет очередь задач ( Task Queue )
Если событий не происходит, эта очередь пуста
В момент, когда происходит событие, его обработчик помещается в конец очереди
![]() |
|---|
☕ 1
В следующем коде есть только одна синхронная операция - это вызов функции message
message ( 'start' )Далее в скрипте запускается множество асинхронных операций setTimeout и fetch
Расставив breakpoints, можно отследить процесс работы Event Loop
Все асинхронные операции передаются в параллельный поток движка
Что там происходит:
- если это таймер, то отсчет временного интервала
- если
fetch, то выполняется запрос на сервер
Когда истечет временной интервал таймера или завершится fetch,
должны быть запущены соответствующие функции обратного вызова ( колбэки )
Однако они должны быть вызваны в основном процессе JS
их вызовы нужно туда "впихнуть"
но сделать это можно только при условии, что сейчас основной поток ничем не занят,
т.е. стек вызовов пуст
поэтому вызовы колбэков помещаются в очередь задач движка,
причем в том порядке, в каком их "выпихивали" в эту очередь API
теперь, как только стек вызовов будет свободен,
в него будут по очереди помещены вызовы ждущих в очереди обработчиков
function message ( text ) {
document.body.innerText += `${text}\n\n`
}
message ( 'start' )
setTimeout ( () => message ( 'timeout 0' ) , 0 )
fetch ( "https://api.github.com/users" )
.then (
response => response.json ()
.then (
users => message ( `1: ${users[0].login}` )
)
)
setTimeout ( () => message ( 'timeout 1' ), 0 )
fetch ( "https://api.github.com/users?since=250" )
.then (
response => response.json ()
.then (
users => message ( `2: ${users[0].login}` )
)
)
setTimeout ( () => message ( 'timeout 2' ), 0 )
fetch ( "https://api.github.com/users?since=300" )
.then (
response => response.json ()
.then (
users => message ( `3: ${users[0].login}` )
)
)
setTimeout ( () => message ( 'timeout 3' ), 0 )"Тяжелый" таск может серьезно задержать выполнение других задач в очереди, которые имели несчастье попасть туда после него...
☕ 2
function message ( text ) {
document.body.innerHTML += `<small>${text}</small><br>`
}
let start = new Date().getTime()
setTimeout (
() => message ( `Timer real time: ${new Date().getTime()-start} ms` ),
0
)
for ( var x=0; x<1000000000; x++ ) continue
message ( "Loop 'for' finished" )В этом примере таймер был установлен на задержку 0 сек
☕ 3
В этом примере таск, который запускается таймером, "подвешивает" работу браузера
Обработчики события click ждут в очереди задач завершения работы таска, запущенного таймером
Если браузер не успел выполнить перерисовку страницы с сообщением "Start" до того, как запустился таск таймера, то мы увидим это сообщение на странице после того, как длинный цикл в таймере завершится...
function message ( text ) {
document.body.innerText += `${text}\n\n`
}
document.body.onclick = (
() => {
let counter = 0
return event => message ( `body clicked ${++counter} times` )
}
)()
setTimeout (
function () {
for ( var x=0; x<10000000000; x++ ) continue
message ( "Counting finished" )
},
0
)
message ( "Start" )Мы уже поняли, что установка таймеров и обработчиков событий UI - это создание тасков
Однако не все задачи равны "по рангу"
Есть макрозадачи и микрозадачи
У них разные очереди, т.е. они никогда не смешиваются
На каждом "витке" Event Loop выполняется одна макрозадача из очереди
После завершения очередной макрозадачи Event Loop "принимается" за очередь микрозадач
Следующий таск из очереди макрозадач не начнет выполняться до тех пор, пока не будут выполнены все микротаски из очереди микрозадач
К числу микрозадач относятся колбэки промисов и MutationObserver
then() промиса, является microtask
Отличие microtask от task ( task === macrotask ) заключается в том, что браузер "впихнет" его между тасками без очереди
Иными ( правильными ) словами, следующий task ( macrotask ) из очереди не будет запущен до тех пор, пока не опустошится очередь microtask-ов
Как только мы запускаем скрипт на исполнение, мы стартуем task
В следующем примере это вызов функции message, установка таймеров и обработчика события click на document.body
Кроме того, мы запускаем три асинхронных операции, используя Fetch API браузера
Но мы знаем, что метод fetch() возвращает промис...
Т.е. как только будет получен ответ сервера, колбэк, который мы передали методу then() промиса, будет запущен сразу после завершения работы текущего таска, "подвинув" следующий за ним таск, даже если тот встал в очередь намного раньше
☕ 4
function message ( text ) {
document.body.innerText += `${text}\n\n`
}
document.body.onclick = (
() => {
let counter = 0
return event => message ( `body clicked ${++counter} times` )
}
)()
message ( 'start' )
setTimeout ( () => message ( 'timeout 0' ) , 3000 )
fetch ( "https://api.github.com/users" )
.then ( response => response.json () )
.then ( users => message ( `1: ${users[0].login}` ) )
setTimeout ( () => message ( 'timeout 1' ), 2000 )
fetch ( "https://api.github.com/users?since=250" )
.then ( response => response.json () )
.then ( users => message ( `2: ${users[0].login}` ) )
setTimeout ( () => message ( 'timeout 2' ), 100 )
fetch ( "https://api.github.com/users?since=300" )
.then ( response => response.json () )
.then ( users => message ( `3: ${users[0].login}` ) )
setTimeout ( () => message ( 'timeout 3' ), 0 )
section.dispatchEvent ( new Event ( "click" ) )Каждый клик будет порождать новый таск, который будет помещен в очередь задач
Если ваш клик опередит завершение операции fetch, то соответствующее сообщение появится раньше, чем имена юзеров гитхаба
Однако колбэки таймеров будут сдвинуты в очереди задач, как только завершится fetch-запрос к серверу
![]() |
|---|
© Irina H.Fylyppova 2018
Использование данных материалов или любой их части коммерческими школами ( курсами ) является нарушением авторских прав
| 1 | 2 | 3 | 4 | 5 |
| 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 |
| ⏬ |
|---|
- Блок-схема алгоритма
- Developer Tools
- Chrome DevTools
- Переменные
- Оператор typeof
- Структуры данных
- Операторы присваивания
- Логические выражения
- Условные операторы
- Инкремент
- Свойство length
- Оператор цикла for
- UTF-8
Homework
- Приведение типов
- NaN | null | Infinity
- BigInt (ES10)
- Функции
- Методы
- Методы строк
- Методы массивов
- Date ()
Самостоятельная работа
Практика (XSS)
Homework
- Циклы while и do...while
- Циклы for...of и for...in
- Параметры по умолчанию
- Объект function
Практика
Homework
- Нативные и host-объекты
- Литерал объекта
- Унаследованные свойства
- Конструктор
- Модель наследования
- Публичные и приватные свойства
- Оператор in
1
Homework
- Итерирующие методы массивов
- Тестирование производительности
- SHA
Homework
- Размеры и прокрутка элемента
- Event Loop
- async | await
- API
- REST | HATEOAS
- status codes
JSON placeholder-
JSON server
fake chat
Homework
- strict mode
- Вычисляемые имена свойств
- Краткий синтаксис методов
- Краткий литерал объекта
- Классы
Homework
- :not(:defined)
- Shadow DOM
- Custom elements
- Lifecycle hooks
- whenDefined
- <template>
- slot
1
2
3
Homework
- npm
- webpack
Упражнение 1- ES6 модули
Упражнение 2- --mode | --watch
Упражнение 3
Упражнение 4
Упражнение 5
Упражнение 6
Упражнение 7
Упражнение 8
Homework
| ⏫ |
|---|





Дополнительно
Справочная инфо
Git Bush
TCP/IP
Коды символов