Галоўная / Артыкулы

Цыкл задач JavaScript: мікрозадачы, макразадачы і стакан вызваў

Выявіце, як обявы, таймеры і функцыі-адрэсаты fetch праходзяць через канеўкі мікразадач і звычных задач, ў результате чаго логі асінхронных задач перестаюць выглядаць абыякава пад час інтэрв’юў та падчас выявлення багоў у працуючым програмнаму забезпечэнні.

Выдавец: REACTAPP.TOP3 хв чытання645 слоў

Крыніцы: Event Loop in JavaScript — JavaScript Interview Questions & Answers — Part 6

Розумэнне ціклу змагання є ключовым для стварэння надзеяных працоўных програм на JavaScript. Сістэма выканання перамешвае сінхронную работу на стаке вызываў з асінхроннымі калбэкамі, якія прыходзяць пазней через API хоста.

Цікл змагання плануе гэтыя асінхронныя калбэкі, калі стак стае пустым.

console.log("start");

setTimeout(function (){
  console.log("timer");
}, 5000);

fetch("https://api.netflix.com")
.then(() => {
  console.log("netflix");
});

console.log("end");

console.log("start") выкананая негайна на стаке, таму спачатку выведаецца start.

setTimeout рэгіструе свой калбэк у хоста (Web API прыгледача або падсістэма таймера Node) і запускае затрымку. JavaScript не павінна зупыняцца на пяць секунд; яна продовжае работу.

fetch таксама перадае запит да сеті хосту. Ядро не чакае адпаведзення, прычымляючыся да наступных задач. Таму end выведаецца, калі таймеры і сетевая работа ўсё яшчэ трываюць.

Якщо сервер адказвае прыблізна за адну секунду, функцыя-адаптаванне пасля выпалення абавязку стае мікрозадачай і патрапляе ў чергу мікрозадач. Мікрозадачы маюцы вышэйшы прыорітет над макрозадачамі (звычайная черга адаптавання/задач). Пасля таго, як сінхронны код завершыўся і стак вызваў стаў пустым, цікл адбытка падзеяў спачатку выканае мікрозадачы, перш чым прыйняць наступную макрозадачу. Таму працоўнік fetch можа выдрукаваць інфармацыю раней, чым спрацюе таймер на пяць секунд.

Калі выпывае таймаут, яго функцыя-адаптаванне патрапляе ў чергу задач. Лячы тады, калі стак і черга мікрозадач стануць пустымі, цікл адбытка падзеяў перакладае гэта адаптаванне на стак, дзе яно выдруквае timer.

Черга мікрозадач проты чергі адаптавання

Мікразадачы маюцы вышэйшую прыоритетнасць чым очерк вызваў (задач). Рэакціі Promise (.then, .catch, .finally) і вызвы MutationObserver ўжо ёсць мікразадачы. setTimeout, setInterval і багато слухачаў запускаюцых змян DOM дадаюць у очерк макразадачы.

Калі стак пустэе, цикл обрабоцуе всі застаўленыя мікразадачы, прычым перш чым выбраць адну макразадачу. Тры застаўленыя мікразадачы выконваюцца адна за іншайя прычым перш чым запрацюе наступны таймер або обработчык кліка.

Калі пачынаецца цикл запуска змян?

Ён не “запускаецца” пасля вашага скрыпта. Цикл ўжо працюе падчас выконання: яго ўсё час перакладваюць, чы пусты стак і чы є задачі ў очэрках.

Чы толькі асінхронныя вызвы хоста выкарыстоўваюць серавысьць хоста?

Так, для шляху абсарбавання. Асінхронныя операцыяй праходзячы через API хоста і абсарбы. Сінхронныя калебэкі, якія пасылаюцца да map, filter або reduce, выконваюцца безпосередна на стэкі і ніколі не рэгіструюцца ў сераўе Web API як адкладзеныя задачы.

Блакуванне галоўнага потока

Усі JavaScript-коды на сторанцы (або галоўны поток рабочага процесу Node) дзеляць аднаковы стэк вызоў. Дужа дзейна сінхронная функцыя x() монаполізуе гэты стэк: нічога іншага — атрыбуты, клікі, іншы JavaScript-код — не выконваецца пакуль яна не вернёцца. Для задач, якія трэба чакаць, лепш выкарыстоўваць асінхронныя API: таймеры, сецьвер-трансляцыі і ввод/вывод файлоў у Node, так каб стэк застаўся вольным для іншых задач.

Web API

setTimeout, дапаможнікі DOM такія як document.getElementById, fetch, localStorage, а таксама console не ўскладневаюць саму мову. Хосты выклікаюць іх разам з дварчыкам JS. У браузерах яны належаць да глобальнага об’екта window. Розумеўшы межу — мова протыва хоста — можна зрозумець, чаму існуюць цыкл супаработак і канэ: ўбезпечваць безпечную пераўязку завершэннях хоста з однопраменным дварчыкам.

У далейшых інтэрв’ю часта спытаюць, што будзе выведвацца, калі Promise.resolve().then(...) пераганяе setTimeout(..., 0). Калебак фармы праўіць, таму што мікрозадачы завершаюцца абоўсумова раней, чым пачнёцца наступная макрозадача, нават калі затрымка таймера становіць нуль. Гэтае адно правілы пояснюе вельмі большую частку багоў у кодзе асінхронных UI, калі даны выведваюцца не па порядку.