Як влаштований Event Loop у JavaScript: паралельна модель та цикл подій

  • 12 января, 08:57
  • 7120
  • 0

 У Event Loop у мові JavaScript полягає секрет асинхронного програмування. Сам собою JS є однопоточним, але з використанні кількох розумних структур даних можна створити ілюзію багатопоточності (паралельна модель). Як це відбувається, розповімо у цій статті.

Код JavaScript працює лише в однопотоковому режимі. Це означає, що в той самий момент може відбуватися тільки одна подія. З одного боку, це добре, оскільки таке обмеження значно спрощує процес програмування, тут не виникає проблем паралелізму. Але, як правило, у більшості браузерів у кожній із вкладок існує свій цикл подій. Середовище керує декількома паралельними циклами.

Спільним знаменником для всіх середовищ є вбудований механізм, який називається Event Loop JavaScript, який обробляє виконання кількох фрагментів програми, викликаючи щоразу ядро (рушій) JS.

Яка ідея циклу подій?

Існує нескінченний цикл подій, в якому JavaScript рушій чекає своє завдання, виконує його і чекає на нове. Алгоритм роботи рушія ми можемо бачити під час перегляду будь-якої веб-сторінки. Він входить у роботу тоді, коли необхідно обробити будь-яку подію чи скрипт. Схема роботи виглядає так:

JavaScript не діє і чекає на своє завдання.

Як тільки завдання з'являються, рушій починає їх виконання, починаючи з першого.

Якщо надійшло нове завдання, але рушій зайнятий виконанням попереднього - воно ставиться в чергу.

Візуально процес можна зобразити так: 

  1. Stack (Стек). Являє собою потік виконання коду JavaScript. Event Loop виконує одне просте завдання – здійснює контроль стеку викликів та черги зворотних викликів. Якщо стек викликів порожній, цикл подій візьме першу подію з черги і відправить її в стек викликів, який запустить. При виклику нового способу зверху стека виділяється окремий блок пам'яті. Стек викликів відповідає за відстеження всіх операцій у черзі, які мають бути здійснені. При завершенні черги вона витягується зі стека.

  1. Heap (Куча). У купі відбувається створення нового об'єкта.
  2. Queue (Черга). Черга подій відповідає за надсилання нових функцій на трек обробки. Він слідує структурі даних черги, щоб підтримувати правильну послідовність, в якій всі операції повинні надійти на виконання. Якщо простіше, то це і є список завдань, які повинні вирушити на обробку і чекають на свій час.
  3. Web API. Не є частиною JavaScript, вони створені на основі JS. Щоразу, коли викликається асинхронна функція, вона відправляється до API браузера. На основі команди, отриманої зі стеку викликів, API запускає власну однопотокову операцію.

Відстеження нових подій у циклі: 

while(queue.waitForMessage()){

  queue.processNextMessage();

}

 Якщо у черзі немає завдань, queue.waitForMessage очікує їх надходження. 

Як події додаються до черги

Всі події в браузерах постійно додаються до черги, якщо вони відбулися або мають свій обробник. setTimeout може додавати подію в чергу не відразу, а після зазначеного часу. Якщо на даний момент у черзі немає подій, воно надійде в обробку відразу.

Коли операція setTimeout обробляється в стеку, вона надсилається відповідному API, який чекає до вказаного часу, щоб відправити цю операцію в обробку. Середовище керує кількома паралельними циклами подій, наприклад для обробки викликів API. Веб-воркери також працюють у своєму циклі подій.

Операція вирушає у чергу подій. Отже, у нас є циклічна схема для виконання асинхронних операцій на JavaScript. Сама мова є однопоточною, але API-інтерфейси браузера діють як окремі потоки.

Цикл подій постійно перевіряє, чи стек викликів порожній. Якщо він порожній, нові функції додаються із черги подій. Якщо це не так, то поточний виклик функції.

Давайте подивимося, як відкласти виконання функції доти, доки стек не очиститься.

Приклад використання setTimeout(() => {}), 0) полягає в тому, щоб викликати функцію, але виконати її після виконання решти всіх функцій у коді.

Приклад: 

const bar = () => console.log('bar')

const baz = () => console.log('baz')

const foo = () => {

  console.log('foo')

  setTimeout(bar, 0)

  baz()

}

foo()
bar, baz, foo – випадкові імена.

 При запуску коду спочатку викликається foo(). Усередині foo() ми спочатку викликаємо setTimeout, передаючи bar як аргумент, і інструктуємо його таким чином, щоб він запускався якнайшвидше, передаючи 0 як таймер. Потім ми викликаємо baz(). 

Порядок функцій у програмі: 


Чому так відбувається?

Черга подій

Під час виклику setTimeout(), браузер або Node.js запускають таймер. Після закінчення таймера (у нашому випадку ми встановили 0) як тайм-аут, функція зворотного виклику міститься в чергу подій.

Черга подій також є місцем, де ініційовані користувачем події (кліки мишею, введення з клавіатури та ін) розміщуються в чергу до того, як код зможе на них відреагувати.

Event Loop надає пріоритет стеку викликів. Спочатку він обробляє все, що знаходить у стеку викликів, а коли там нічого не лишається, переходить до обробки черги подій.

setTimeout з аргументом 0 не гарантує, що обробка буде виконана миттєво. Все залежить від того, скільки завдань зараз перебуває в черзі. У прикладі нижче ”message” буде виведено швидше обробника callback_1. Пояснюється це тим, що затримка є мінімальним часом, необхідним середовищі на виконання запиту. 

(function () {

  console.log('start');

  setTimeout(function callback() {

    console.log('message from callback');

  });

  console.log('message');

  setTimeout(function callback_1() {

    console.log('message from callback_1');

  }, 0);

  console.log('finish');

})();

// "start"

// "message"

// "finish"

// "message from callback"

// "message from callback_1"

Цикл подій JavaScript відрізняється від інших мов тим, що його потік виконання ніколи не блокується, крім деяких винятків, таких як alert або синхронний HTTP-запит, які не рекомендується використовувати. Тому навіть коли програма очікує запити зі сховища або відповідь із сервера, вона може обробляти інші процеси, наприклад введення користувача.

Заключення

Веб-сайти стали більш інтерактивними і динамічними, необхідність виконання інтенсивних операцій стала більш актуальною (наприклад, виконання зовнішніх мережевих запитів для отримання даних API). Щоб обробляти ці операції, потрібне використання методів асинхронного програмування. Вбудований механізм Event Loop допомагає JavaScript обробляти асинхронний код. 


0 комментариев
Сортировка:
Добавить комментарий

IT Новости

Смотреть все