[{"data":1,"prerenderedAt":54},["ShallowReactive",2],{"post-fr-quand-on-dit-event-loop":3,"translation-fr-quand-on-dit-event-loop":53},["Reactive",4],{"id":5,"date":6,"date_gmt":7,"guid":8,"modified":7,"modified_gmt":7,"slug":10,"status":11,"type":12,"link":9,"title":13,"content":15,"excerpt":18,"author":20,"featured_media":20,"comment_status":21,"ping_status":21,"sticky":17,"template":19,"format":22,"meta":23,"categories":24,"tags":26,"tagsResolved":27,"translations":40},10542940,"28. 09. 2026","2026-09-28T00:00:00",{"rendered":9},"local:kdyz-se-rekne-event-loop","kdyz-se-rekne-event-loop","publish","post",{"rendered":14},"Když se řekne Event Loop",{"rendered":16,"protected":17},"\u003Cp>Práce softwarového vývojáře vyžaduje, abychom se učili novým věcem a sledovali trendy v oboru. Ať už z důvodu zlepšení si pozice na pracovním trhu nebo jen pro zvýšení svých znalostí a produktivity. V posledních dvou letech se k tomu, co musíme sledovat, nejdříve objevily první náznaky umělé inteligence v podobě různých AI asistentů, co-pilotů a integrací ChatGPT, které umí nahradit běžné a rutinní procesy, ale i generovat řešení složitějších problémů. Dnes, v době psaní tohoto textu, dokážou přední jazykové modely na základě jednoho promptu v Claude Code Fable nebo ChatGPT Astra vygenerovat celý projekt, který lze deploynout, otestovat a spustit. Od softwaru, medicíny až po copywriting a překládání, nyní čelí v těchto oborech profesionálové a profesionálky vlnám propouštění. O této tzv. \u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTechnological_unemployment\">technologické nezaměstnanosti\u003C\u002Fa> se už nemluví pouze na akademické půdě. Začalo být tématem a každodenní realitou bílolímečkových oborů viz \u003Ca href=\"https:\u002F\u002Fwww.technologyreview.com\u002F2024\u002F01\u002F27\u002F1087041\u002Ftechnological-unemployment-elon-musk-jobs-ai\u002F\">zde\u003C\u002Fa> nebo \u003Ca href=\"https:\u002F\u002Fwww.ilo.org\u002Fresource\u002Farticle\u002Fminimizing-negative-effects-ai-induced-technological-unemployment\">zde\u003C\u002Fa>. Není to poprvé, ani naposled, kdy pokroky v technologiích mnoho zaměstnání a oborů dělá irelevantní, a naopak vytváří nové. V takových případech dochází k oddělování \u003Ca href=\"https:\u002F\u002Fdictionary.cambridge.org\u002Fdictionary\u002Fenglish\u002Fseparate-the-wheat-from-the-chaff\">zrna od plev\u003C\u002Fa> a je legitimní se ptát, kdo zůstane v této AI-powered tržní ekonomice pro korporace užitečný?\u003C\u002Fp>\n\u003Cp>Juniorní pozice se zdají být prvním a přirozeným terčem. Povrchní znalosti, nerozvinutá schopnost vidět technické problémy v širším kontextu technických, ale i byznysových požadavů. Tím AI přestává trpět, kdežto juniorní pozice jsou tímto známé.\u003C\u002Fp>\n\u003Cp>Podívám-li se na svůj obor front-endu a back-endových serverových runtimů node\u002Fdeno\u002Fbun, hlubší znalost Event Loopu mi přijde, že je jedna z několika tzv. „hard skills“, které v prostředí Javascriptu oddělují juniorního kodéra od seniorního softwarového inženýra. Pochopením Event Loopu dokáže člověk odpovědět na několik důležitých otázek, například: jak je možné, že Javascript je single-threaded programovací jazyk, přesto v něm děláme několik věcí naráz pomocí asynchronních operací? O Event Loopu lze psát leccos. Mé ambice jsou skromné. Podívám se na Event Loop takovým způsobem, abych dokázal vysvětlit jednu z nejběžnějších otázek na pracovním pohovoru na pozici front-end vývojářem: v jakém pořadí se provedou následující operace a proč:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-js\">setTimeout(() =&gt; {\n  console.log('hey, from set timeout');\n}, 0);\n\nnew Promise((resolve) =&gt; resolve('ahoy from promise')).then((v) =&gt;\n  console.log(v)\n);\n\nconsole.log('hello from console log');\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Technický úvod do problému\u003C\u002Fh2>\n\u003Cp>Javascript je \u003Cstrong>single-threaded\u003C\u002Fstrong>. Pokud nepočítáme \u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWeb_Workers_API\">Web Workers API\u003C\u002Fa>, znamená to, že Javascript umí zpracovávat v jeden okamžik pouze jedinou operaci. Javascript je navržen tak, že zpracovává operace sekvenčně metodou LIFO (Last-In, First Out), a dokud operace není dokončena, blokuje všechny následující operace. Za normálních okolností by například po kliknutí na tlačítko blokoval jeden pomalý GET request na vzdálené API veškerou uživatelskou interakci; celé uživatelské rozhraní prohlížeče by zamrzlo, dokud by se síťový požadavek (network request) nevyřídil. Když v devadesátých letech navrhl Brendan Eich Javascript, bylo tehdy v Javascriptu zpracování operací synchronní, s omezenými možnostmi zpracování událostí, které vyvolala uživatelská interakce s prohlížečem. Stálo by za historickou sondu, proč Eich takto Javascript navrhl. V současné době ale víme, že na Javascriptu založené webové stránky (tzn. celý web) se takto nechovají. Uživatel může interagovat s více částmi webové stránky najednou. Jak toho může single-threaded programovací jazyk dosáhnout?\u003C\u002Fp>\n\u003Ch2>Enter Javascript runtime\u003C\u002Fh2>\n\u003Cp>Když se řekne Javascript, musíme si uvědomit, že se jedná o \u003Cstrong>kolekci knihoven a komponent\u003C\u002Fstrong>. Teprve jejich propojením získáme funkční celek – \u003Cstrong>runtime\u003C\u002Fstrong>, tedy prostředí, ve kterém běží naše aplikace. V současné době máme na výběr ze dvou hlavních typů runtime. Buď se jedná o runtime webového prohlížeče, nebo o runtime jedné z několika serverových implementací, například Node.js, Deno, či nejnovější Bun. Přestože runtime v prohlížečích a na serverech má společné prvky, najdeme v nich i důležité rozdíly. Ty vyplývají ze zaměření daného typu runtime. Webové prohlížeče se orientují na práci s DOM (\u003Ccode>window\u003C\u002Fcode>, \u003Ccode>document\u003C\u002Fcode>), uživatelské interakce (\u003Ccode>addEventListener\u003C\u002Fcode>), síťové operace pomocí \u003Ccode>fetch\u003C\u002Fcode> nebo \u003Ccode>XMLHttpRequest\u003C\u002Fcode>, lokální úložiště v prohlížeči (\u003Ccode>localStorage\u003C\u002Fcode>, \u003Ccode>sessionStorage\u003C\u002Fcode>) aj. Zatímco runtime Node.js nebo Bun se orientuje na prostředí serverových aplikací, práci se souborovým systémem, databázemi, nebo low-level operace nad operačním systémem. \u003Cstrong>Oba typy runtime ale implementují Event Loop\u003C\u002Fstrong>. Pro zjednodušení se dále budu zajímat pouze o runtime webových prohlížečů.\u003C\u002Fp>\n\u003Ch2>Runtime webových prohlížečů\u003C\u002Fh2>\n\u003Cp>Javascriptový runtime webových prohlížečů se skládá z následujících částí:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Javascript Engine\u003C\u002Fli>\n\u003Cli>Web APIs\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Event Loop\u003C\u002Fstrong>\u003C\u002Fli>\n\u003Cli>Task queue a Microtask queue\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Event Loop je pouze jedna z komponent a přichází do hry pouze v případě zpracování asynchronního kódu. Pokud v našem Javascriptovém programu používáme pouze synchronní operace, Event Loop pak nebude nikdy využitý. Přesto Event Loop patří mezi hlavní charakteristiky Javascriptu a díky němu jsou současné moderní webové aplikace plné asynchronních funkcionalit. Pro pochopení Event Loopu a zpracování asynchronního kódu v Javascriptu vůbec, je dobré si připomenout, \u003Cstrong>jak se chová Javascript při plně synchronních operacích\u003C\u002Fstrong>. Na to je dobré pak pochopit fungování Javascript enginu.\u003C\u002Fp>\n\u003Ch3>Javascript engine\u003C\u002Fh3>\n\u003Cp>Pomyslným srdcem Javascriptu je engine. Hlavní funkce JavaScript enginu jsou parsování, interpretace a exekuce kódu. Parsingem kódu myslíme \u003Cstrong>převod zdrojového kódu JavaScriptu na tzv. abstraktní syntaktický strom (AST)\u003C\u002Fstrong>, což je struktura, kterou engine dokáže zpracovat. Interpretací myslíme to, že \u003Cstrong>většina moderních enginů nejdříve kód interpretuje a poté kompiluje do strojového kódu (just-in-time compilation – JIT)\u003C\u002Fstrong>. Exekucí kódu pak myslíme, že \u003Cstrong>po překladu do strojového kódu začne engine provádět instrukce a zajistí běh aplikace\u003C\u002Fstrong>.\u003C\u002Fp>\n\u003Cp>Příklady populárních JavaScriptových enginů:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>V8: Používá jej Google Chrome a Node.js. Je známý svou rychlostí a efektivitou.\u003C\u002Fli>\n\u003Cli>SpiderMonkey: První JavaScriptový engine, vyvinutý společností Netscape, dnes používán ve Firefoxu.\u003C\u002Fli>\n\u003Cli>JavaScriptCore (aka Nitro): Používá jej Safari a další produkty od Apple.\u003C\u002Fli>\n\u003Cli>Chakra: Používal jej Microsoft Edge (legacy), nyní je nahrazen enginem V8 v novém Edge (Chromium).\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>JavaScript engine dále poskytuje dvě paměťové struktury: \u003Cstrong>heap\u003C\u002Fstrong> a \u003Cstrong>call stack\u003C\u002Fstrong>. Jejich rozdíly a fungování by si opět zasloužily samostatný článek na téma Memory managementu v Javascriptu. Základní definici si přečtětě na Wikipedii nebo vypromptujte s Chat GPT.\u003C\u002Fp>\n\u003Cp>Co je pro pochopení Event Loopu důležité je, že \u003Cstrong>Javascript Engine jako takový neimplementuje zpracování asynchronního kódu\u003C\u002Fstrong>. Veškerou asynchronicitu Javascriptu je nutné implementovat jinde. Kde jinde? To má právě na starosti Javascript Runtime, který k Javascript enginu přidává Web APIs (jsme v browseru), Event loop a čekací fronty Task queue a Microtask queue. Fun fact: pokud by Javascript byl čistě synchronní programovací jazyk, vystačil by si pouze s heap a call stackem a nepotřeboval by Event Loop. Ale to předbíhám.\u003C\u002Fp>\n\u003Ch3>Heap\u003C\u002Fh3>\n\u003Cp>\u003Cstrong>Heap\u003C\u002Fstrong> co se týče pochopení Event Loopu není až tak důležitý. Heap je paměťová struktura, kde Javascript Engine ukládá komplexní datové typy (Object, Array, Function, Map, Set, WeakMap, WeakSet). Velikost heapu je dána velikostí paměti RAM. Narozdíl od call stacku není heap organizován sekvenčně, je to nelineární paměť. Do heapu se ukládají data, jejichž typ a velikost neznáme během kompilace a mohou se měnit během runtime (přiřazení jiných hodnot properties objektů, vznik nových properties, nových objektů apod.), takže heap je dynamická paměť. Heap je technicky vzato implementace běžného návrhového vzoru dynamické alokace paměti (dynamic memory allocation pattern), což znamená, že vyžaduje Garbage Collector, který čistí alokovanou paměť během runtime podle toho, zda nějaký kus programu vyžaduje (má reference v call stacku) na objekty alokované v heap. Pokud ne, Garbage Collector za normálních okolností pročistí heap a uvolní paměť. Garbage Collector ale není 100% přesný, tudíž i přes jeho existenci stále může dojít k únikům paměti (memory leaks).\u003C\u002Fp>\n\u003Ch3>Call Stack\u003C\u002Fh3>\n\u003Cp>Na druhé straně je \u003Cstrong>call stack\u003C\u002Fstrong> jednodušší statická paměť a mnohem zajímavější pro vysvětlení Event Loopu, protože má co dočinění s pořadím toho, jak se v Javascriptu vykonává kód. Javascript engine pracuje s call stackem pouze synchronním způsobem krok za krokem. Po technické stránce je call stack implementací klasického \u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FStack_(abstract_data_type)\">návrhového vzoru Zásobníku (Stacku)\u003C\u002Fa>, kdy poslední přidaný element do paměti je zpracováván jako první (Last In, First Out, LIFO). A co přesně je v call stacku uloženo a určuje pořadí \u003Cstrong>čeho\u003C\u002Fstrong> přesně? Pro zjednodušení je možné si představit Javascriptový program jako kolekce funkcí a proměnných a call stack zajišťuje, aby naše funkce byly volány a proměnné byly deklarovány ve správném pořadí a ve správný čas.\u003C\u002Fp>\n\u003Cp>Zatímco komplexní datové typy (objekty, pole atd.) jsou uložené v heapu, v call stacku se ukládají pouze reference\u002Fpointery do heapu. Co je naopak v call stacku reálně uložené, jsou hodnoty primitivních datových typů (string, integer, boolean, atd.), ale celý \u003Cstrong>exekuční kontext\u003C\u002Fstrong> funkce, čímž se myslí vše,, „co je potřeba k volání funkce“. Opět by bylo vhodné se hlouběji ponořit do toho, co všechno exekuční kontext funkce znamená, ale pro zjednodušení exekuční kontext zahrnuje:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Variable Environment (Prostředí proměnných)\u003C\u002Fstrong>: Záznam o proměnných a deklaracích funkcí specifických pro danou funkci.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Lexical Environment (Lexikální prostředí)\u003C\u002Fstrong>: Odkazy na nadřazený scope (lexikální prostředí) a proměnné definované pomocí let a const.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Vazba this\u003C\u002Fstrong>: Hodnota this v aktuální funkci.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Specifické detaily volání\u003C\u002Fstrong>: Argumenty předané funkci a mechanismus jejího návratu (tzn. místo v programu, kam se má program vrátit po dokončení funkce).\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>Když nechám Claude vygenerovat náhodný příklad pro ukázání, jak funguje call stack, mějme třeba následující kód:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-js\">function multiply(x, y) {\n  const result = x * y;\n  return result;\n}\n\nfunction calculate() {\n  const a = 10;\n  const b = 20;\n  const product = multiply(a, b);\n  console.log(product);\n}\n\ncalculate();\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Co se v takto jednoduchém příkladu přesně děje? Situace je následující: do \u003Cstrong>call stacku\u003C\u002Fstrong> se nejdříve uloží funkce \u003Ccode>calculate()\u003C\u002Fcode>, respektive její celý exekuční kontext. Poté se do \u003Cstrong>call stacku\u003C\u002Fstrong> přidá exekuční context funkce \u003Ccode>multiply()\u003C\u002Fcode>. Jakmile funkce \u003Ccode>multiply()\u003C\u002Fcode> je zavolána a její výsledek přiřazen do proměnné \u003Ccode>product\u003C\u002Fcode>, funkce \u003Ccode>multiply()\u003C\u002Fcode> a jeji exekuční kontext jsou odebrány z call stacku. Pak se do call stacku uloží exekuční kontext \u003Ccode>console.log()\u003C\u002Fcode>. Po vypsání hodnoty „200“ do konzole prohlížeče se exekuční kontext \u003Ccode>console.log()\u003C\u002Fcode> z call stacku odebere. Nakonec se odebere i exekuční kontext funkce \u003Ccode>calculate()\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Všimněme si, že v příkladu nejsou globální proměnné a že volání funkce calculate() samo o sobě není zdánlivě v žádné funkce, jejíž exekuční kontext by mohlo být přidán do call stacku. Jak se tedy globální proměnné a volání funkcí v globálním scope dostane do call stacku?\u003C\u002Fp>\n\u003Ch4>Global Execution Context (EGC)\u003C\u002Fh4>\n\u003Cp>Vše, co je v globálním scope, lze chápat tak, že se volá uvnitř jedné hlavní funkce, jejíž exekuční kontext má speciální název i chování: \u003Cstrong>global execution context\u003C\u002Fstrong>. Upravme výše použitý příklad a přidáme do něj globální proměnné.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-js\">var globalA = 'testGlobalA';\nconst globalB = 'testGlobalB';\n\nfunction multiply(x, y) {\n  const result = x * y;\n  return result;\n}\n\nfunction calculate() {\n  const a = 10;\n  const b = 20;\n  const product = multiply(a, b);\n  console.log(product);\n}\n\ncalculate();\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Budeme-li brát, že globální kontext našich skriptů se bere jako jakási „main“ funkce, náš skript se bude chovat následovně: do call stacku se přidá globální exekuční kontext, do \u003Cstrong>prostředí proměnných\u003C\u002Fstrong> se přidá proměnná „globalA“, do \u003Cstrong>lexikální prostředí se přidá\u003C\u002Fstrong> proměnná „globalB“, vytvoří se \u003Cstrong>vazba this\u003C\u002Fstrong>, která bude v prostředí prohlížeče odkazovat na globální objekt \u003Cstrong>window\u003C\u002Fstrong>. Co se týče objektu \u003Cstrong>window\u003C\u002Fstrong>, je dobré připomenout, že zatímco \u003Cstrong>var global A\u003C\u002Fstrong> se přidá jako property do objektu \u003Cstrong>window\u003C\u002Fstrong>, proměnné definované pomocí \u003Cstrong>const\u003C\u002Fstrong> nebo \u003Cstrong>let\u003C\u002Fstrong> se přidají do globálního scope\u002Fglobálního lexikálního prostředí, ale nikoli do globálního objektu \u003Cstrong>window\u003C\u002Fstrong>.\u003C\u002Fp>\n\u003Cp>Jakmile všechny funkce definované „uvnitř“ globálního exekučního kontextu jsou přidané do call stacku, zpracované, a odebraného z call stacku, zůstává v call stacku pouze globální exekuční kontext coby poslední funkce, a ta je nakonec také z call stacku odebrána, a call stack je prázdný.\u003C\u002Fp>\n\u003Cp>Takto to aspoň funguje v principu, pokud veškerý kód, který Javascript engine zpracovává, je \u003Cstrong>synchronní\u003C\u002Fstrong>. Pokud v naší aplikaci (čti uvnitř globálního exekučního kontextu) máme nějaké funkce, které jsou zpracovávány asynchronně, situace je značně odlišná.\u003C\u002Fp>\n\u003Cp>Řekli jsme, že \u003Cstrong>global execution context\u003C\u002Fstrong> (EGC) je jakousi prvotní „main“ funkcí a vstupním bodem aplikace. Zdá se, že musí běžet celou dobu, dokud není celý kód zpracován. Jak je to ale s asynchronními částmi kódu? Ty přece mohou spustit další funkce, které vracejí nové hodnoty a vytváří nové exekuční kontexty, takže globální exekuční kontext musí být v call stacku „nějak“ přítomen. A jak a kdy se volá asynchronní kód, když je call stack zaplněný částečně, nebo když je zcela prázdný? A pokud je call stack prázdný, jak je možné, aby se v Javascriptu něco spustilo, například, když uživatel klikne na tlačítko a spustí se callback event listeneru registrovaného pomocí addEventListener()? A co monitoruje asynchronní callbacky addEventListeneru, když Javascript engine je zcela synchronní, ba ani neobsahuje žádnou addEventListener funkci?\u003C\u002Fp>\n\u003Ch2>Asynchronie v Javascriptu aneb Web APIs, Task queues, Event loop\u003C\u002Fh2>\n\u003Cp>Engine programovacího jazyka Javascript neobsahuje ani objekt \u003Ccode>console\u003C\u002Fcode> ani její metodu \u003Ccode>console.log()\u003C\u002Fcode>. Což může být překvapení. Faktem je, že pro programátory běžné funkcionality typu \u003Ccode>setTimeout\u003C\u002Fcode>, \u003Ccode>addEventListener\u003C\u002Fcode>, operace pro manipulaci \u003Ccode>DOM\u003C\u002Fcode>, asynchronní síťové requesty \u003Ccode>fetch\u003C\u002Fcode> nebo \u003Ccode>XMLHttpRequest\u003C\u002Fcode> jsou součástí nikoli Javascript enginu, ale další komponenty Javascript runtime: Web API.\u003C\u002Fp>\n\u003Ch3>Web API\u003C\u002Fh3>\n\u003Cp>Jak přesně Javascript engine dokáže spolupracovat s Web API tak, aby Engine měl přístup k funkcím \u003Ccode>console.log()\u003C\u002Fcode> nebo \u003Ccode>setTimeout()\u003C\u002Fcode>, pokud nejsou přímo součástí engine? Javascript Engine (např. V8) má \u003Cstrong>interní registr nativních funkcí\u003C\u002Fstrong>, které jsou definovány v ECMAScript specifikaci (např. Math.random(), Array.prototype.map(), Date.now()). Podle toho Engine rozpozná, že \u003Ccode>setTimeout()\u003C\u002Fcode> není nativní funkcí, a proto si sáhne do globálního scope, respektive objektu \u003Ccode>window\u003C\u002Fcode>, a zkusí zavolat metodu \u003Ccode>window.setTimeout()\u003C\u002Fcode>. Javascript runtime totiž ještě před inicializací programu zajistí, že globální exekuční kontext (a tedy lexikální prostředí každé funkce, viz výše) obsahuje objekty, které jsou součástí Web API, např. document, navigator, performance, Intl, crypto, console.\u003C\u002Fp>\n\u003Cp>Když jsem se ptal ChatGPT, aby mi vysvětlilo proces spuštění metody \u003Ccode>console.log()\u003C\u002Fcode>, napsalo mi chybně, že \u003Ccode>console.log()\u003C\u002Fcode> je součástí samotného Javascript enginu. Když jsem upozornil na to, že tomu tak není, ChatGPT se omluvilo a nabídlo mi přesnější odpověď. Zajímalo mě, proč udělalo chybu. Svedlo to na to, že údajně mezi vývojáři (a tedy i blogery) je rozšířený mentální model fungování Javascriptu, kdy \u003Ccode>console.log()\u003C\u002Fcode> je synchronní operace, tudíž je zpracována přímo na call stacku (což je pravda), a z toho vyplývá, že \u003Cstrong>pokud se něco zpracovává přímo na call stacku (a nepotřebuje callbacky, které se zpracovávají asynchronně mimo hlavní vlákno Javascriptu, a tedy ani Event loop), musí to být součástí Javascript enginu.\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>To je chyba. Metoda \u003Ccode>console.log()\u003C\u002Fcode>neexistuje v Javascript enginu, ten o ní nic neví, kromě toho, že není nativní funkcí. Její konkrétní zpracování předává do Javascript runtime, kde prohlížeč (nebo nodejs) pak provede funkci pomocí nízkoúrovňového kódu v jazyce C++ pomocí C++ bindingů, což jsou vztahy mezi Javascript funkcí a C++ funkcí, která reálně vykonává to, co chceme po Javascript funkci.\u003C\u002Fp>\n\u003Cp>Zhruba již víme, jak to funguje, když Javascript engine potřebuje volat ne-nativní funkcí z Web API. Jak je to ale s tou asynchronií a voláním \u003Ccode>setTimeout()\u003C\u002Fcode>, \u003Ccode>addEventListener()\u003C\u002Fcode> či \u003Ccode>fetch()\u003C\u002Fcode> ?\u003C\u002Fp>\n\u003Ch3>Callbacks, Task Queues obecně\u003C\u002Fh3>\n\u003Cp>Jako single-threaded jazyk potřebuje Javascript engine delegovat vykonání asychronních operací mimo sebe. Javascript runtime obsahuje vedle Web API další komponenty, které asynchronní operace umožňují: Task queues a Event loop. Všechny asynchronní operace v Javascriptu jsou řešeny pomocí callbacků (a to i \u003Ccode>await\u002Fasync\u003C\u002Fcode>, které jsou sugar code využívající Promise-based asynchronicitu). Když Engine narazí na asynchronní operaci, předá její zpracování kompletně na Web API. Pokud se jedná o časovač jako \u003Ccode>setTimeout()\u003C\u002Fcode>, Web API čeká příslušný počet milisekund; pokud se jedná o network request jako \u003Ccode>fetch\u003C\u002Fcode>, opět je to na Web API, aby počkalo na příslušnou odpověď tohoto network requestu. Jakmile Web API dokončí zpracování samotné asynchronní operace, vezme programátorem definovaný \u003Cstrong>callback\u003C\u002Fstrong> a tuto callback funkci předá do speciální komponenty Javascript runtime, fronty nazvané \u003Cstrong>Task queue\u003C\u002Fstrong> anebo \u003Cstrong>Microtask\u003C\u002Fstrong> \u003Cstrong>queue\u003C\u002Fstrong>.\u003C\u002Fp>\n\u003Cp>Callbacky zůstávají ve frontách, dokud není call stack zcela prázdný, respektive, pokud v něm nezůstává pouze globální exekuční kontext (GEK). Jakmile je v call stacku pouze GEK, příchází na řadu další komponenta: Event Loop.\u003C\u002Fp>\n\u003Ch3>Task queue\u003C\u002Fh3>\n\u003Cp>Task queue je starší z obou front a v jistém smyslu je stejně stará jako Javascript sám. Už první verze Javascriptu v Netscape Navigatoru uměly \u003Ccode>setTimeout()\u003C\u002Fcode> a reagovat na události vyvolané uživatelem, a obojí potřebovalo místo, kde callback počká, dokud engine nedokončí to, co právě dělá. Formálně ale Task queue (a Event loop vůbec) popsala až specifikace HTML5 organizace WHATWG, která sjednotila, jak se v tomto ohledu mají chovat všechny prohlížeče.\u003C\u002Fp>\n\u003Cp>Důvod vzniku Task queue je ten, se kterým jsme začali: single-threaded engine nesmí na nic čekat. Web API proto řeší čekání mimo engine a do Task queue pak předá už jen hotový callback, který se zpracuje, až na něj přijde řada. Do Task queue (specifikace HTML jim říká \u003Cstrong>tasks\u003C\u002Fstrong>, v blozích se často setkáme i s označením \u003Cstrong>macrotasks\u003C\u002Fstrong>) patří:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>callbacky časovačů \u003Ccode>setTimeout()\u003C\u002Fcode> a \u003Ccode>setInterval()\u003C\u002Fcode>,\u003C\u002Fli>\n\u003Cli>callbacky událostí registrovaných pomocí \u003Ccode>addEventListener()\u003C\u002Fcode> (kliknutí, stisk klávesy, scroll…),\u003C\u002Fli>\n\u003Cli>síťové události starších API, například \u003Ccode>onload\u003C\u002Fcode> u \u003Ccode>XMLHttpRequest\u003C\u002Fcode> nebo zprávy z \u003Ccode>WebSocket\u003C\u002Fcode>,\u003C\u002Fli>\n\u003Cli>zprávy mezi okny a workery posílané pomocí \u003Ccode>postMessage()\u003C\u002Fcode>,\u003C\u002Fli>\n\u003Cli>a vůbec samotné spuštění skriptu z tagu \u003Ccode>&lt;script&gt;\u003C\u002Fcode> – i náš globální exekuční kontext tedy začíná svůj život jako task.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Za zmínku stojí dvě věci. Zaprvé, „Task queue“ v jednotném čísle je zjednodušení. Specifikace dovoluje prohlížeči mít front několik (např. zvlášť pro uživatelské interakce a zvlášť pro časovače) a vybírat mezi nimi, takže prohlížeč může dát přednost kliknutí před časovačem. V rámci jedné fronty ale platí metoda FIFO. Zadruhé, \u003Cstrong>Event loop vezme z Task queue vždy jen jeden task\u003C\u002Fstrong> a mezi dvěma tasky má prohlížeč příležitost překreslit stránku. I \u003Ccode>setTimeout(callback, 0)\u003C\u002Fcode> proto neznamená „hned“, ale „nejdříve v příštím kole Event loopu“. Při hluboce vnořeném volání časovačů navíc prohlížeče zvedají minimální prodlevu na 4 ms.\u003C\u002Fp>\n\u003Ch3>Microtask queue\u003C\u002Fh3>\n\u003Cp>Microtask queue je o dost mladší. Pojem \u003Cstrong>microtask\u003C\u002Fstrong> se objevil kolem roku 2011, kdy inženýři z Mozilly a Chromia navrhovali náhradu za tzv. Mutation Events – starý mechanismus, kterým šlo sledovat změny v DOM. Mutation Events se spouštěly synchronně při každé jednotlivé změně DOM, což bylo pomalé a náchylné k chybám. Nové API \u003Ccode>MutationObserver\u003C\u002Fcode> mělo změny doručovat asynchronně a najednou v dávce, ale zároveň dříve, než prohlížeč stránku překreslí nebo zpracuje další událost. Task queue na to nestačila, protože mezi dvěma tasky už k překreslení nebo ke zpracování dalšího kliknutí dojít může. Proto vznikla nová fronta, jejíž callbacky se zpracují \u003Cstrong>hned, jakmile se call stack vyprázdní, ještě před dalším taskem\u003C\u002Fstrong>.\u003C\u002Fp>\n\u003Cp>Druhým a dnes nejznámějším využitím Microtask queue se staly \u003Cstrong>Promises\u003C\u002Fstrong>, standardizované v ECMAScript 2015 (ES6). Specifikace ECMAScriptu jim říká „jobs“, prohlížeč je ale řadí právě do Microtask queue. Promises tím dostaly důležitou garanci: callback v \u003Ccode>.then()\u003C\u002Fcode> se nikdy nezavolá synchronně, ale ani zbytečně pozdě – vždy hned po dokončení aktuálně běžícího kódu. Kolem roku 2019 pak prohlížeče přidaly funkci \u003Ccode>queueMicrotask()\u003C\u002Fcode>, pomocí které může microtask naplánovat přímo i programátor, bez obcházení přes Promise.\u003C\u002Fp>\n\u003Cp>Do Microtask queue patří:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>callbacky Promise: \u003Ccode>.then()\u003C\u002Fcode>, \u003Ccode>.catch()\u003C\u002Fcode>, \u003Ccode>.finally()\u003C\u002Fcode>,\u003C\u002Fli>\n\u003Cli>pokračování async funkce za \u003Ccode>await\u003C\u002Fcode> (async\u002Fawait je postavené nad Promises),\u003C\u002Fli>\n\u003Cli>callbacky předané do \u003Ccode>queueMicrotask()\u003C\u002Fcode>,\u003C\u002Fli>\n\u003Cli>callbacky \u003Ccode>MutationObserver\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>A ještě jedno důležité pravidlo: zatímco z Task queue bere Event loop vždy v daný okamžik právě jeden task, \u003Cstrong>Microtask queue vyprázdní vždy celou\u003C\u002Fstrong>, včetně microtasků, které přibyly během jejího zpracování. Pokud by nějaký microtask přidal další microtask, prohlížeč by se ke kliknutí ani k překreslení stránky už nedostal a stránka by zamrzla – stejně jako při nekonečném cyklu v synchronním kódu.\u003C\u002Fp>\n\u003Ch2>Event Loop\u003C\u002Fh2>\n\u003Cp>Mysteriózní Event loop se nyní ukáže jako něco relativně triviálního. Event loop není nic jiného než jedna komponenta v Javascript runtime, která neustále kontroluje, zda call stack je plný. Pokud je prázdný, je dalším úkolem Event loopu rozhodnout, zda vzít metodou FIFO první callback z Task queue nebo Microtask queue. Toť vše.\u003C\u002Fp>\n\u003Csvg id=\"el-anim\" xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" viewBox=\"0 0 480 764\" role=\"img\" aria-label=\"Animace: jak Event Loop zpracuje úvodní příklad\" aria-describedby=\"el-anim-desc\" style=\"display:block;width:100%;max-width:560px;height:auto;margin:2.5rem auto\">\n\u003Cdesc id=\"el-anim-desc\">Skript běží v globálním exekučním kontextu. setTimeout předá časovač Web API a jeho callback se po 0 ms zařadí do Task queue. Callback z .then() splněné Promise se zařadí do Microtask queue. console.log proběhne synchronně a do konzole vypíše „hello from console log“. Když se call stack vyprázdní, Event Loop nejdřív zpracuje Microtask queue a vypíše „ahoy from promise“, teprve potom vezme task z Task queue a vypíše „hey, from set timeout“.\u003C\u002Fdesc>\n\u003Cstyle>\n#el-anim{font-family:\"EB Garamond\",Georgia,serif}\n#el-anim .mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;fill:#1a1a1a}\n#el-anim .code{font-size:12px}\n#el-anim .ui{font-family:system-ui,-apple-system,\"Segoe UI\",Roboto,sans-serif;font-size:11px;fill:#5f6368}\n#el-anim .panel{fill:#fafafa;stroke:#d0d0d0;stroke-width:1.5}\n#el-anim .label{font-size:16px;font-weight:600;fill:#1a1a1a}\n#el-anim .hint{font-size:12px;fill:#8a8a8a}\n#el-anim .cap text{font-size:15px;fill:#1a1a1a}\n#el-anim .frame rect{fill:#eeeeee;stroke:#8a8a8a;stroke-width:1.2}\n#el-anim .cb rect{fill:#e6f0e6;stroke:#006400;stroke-width:1.2}\n#el-anim .timer rect{fill:#fff6db;stroke:#b8860b;stroke-width:1.2}\n#el-anim .hl{fill:#fff1a8}\n#el-anim .rule{stroke:#e8eaed;stroke-width:1}\n#el-anim .anim{animation-duration:30s;animation-iteration-count:infinite;animation-timing-function:ease-in-out;animation-fill-mode:both}\n#el-anim .btn{cursor:pointer}\n#el-anim .btn:focus{outline:none}\n#el-anim .btn rect{fill:#ffffff;stroke:#c8c8c8;stroke-width:1.2}\n#el-anim .btn:hover rect,#el-anim .btn:focus-visible rect{fill:#eef6ee;stroke:#006400}\n#el-anim .btn text{font-size:14px;fill:#1a1a1a}\n#el-anim .btn .ico{fill:#1a1a1a}\n#el-anim .tok,#el-anim .hl,#el-anim .cap{opacity:0}\n#el-anim .out,#el-anim .cap-end{opacity:1}\n#el-anim .loop{color:#9a9a9a}\n#el-anim .spin{transform-box:fill-box;transform-origin:center;animation:elAnim-spin 3s linear infinite}\n#el-anim .caret{animation:elAnim-blink 1.1s steps(1) infinite}\n#el-anim .g-global{animation-name:elAnim-global}\n#el-anim .g-set{animation-name:elAnim-set}\n#el-anim .g-timer{animation-name:elAnim-timer}\n#el-anim .g-cb1{animation-name:elAnim-cb1}\n#el-anim .g-prom{animation-name:elAnim-prom}\n#el-anim .g-cb2{animation-name:elAnim-cb2}\n#el-anim .g-log{animation-name:elAnim-log}\n#el-anim .g-in2{animation-name:elAnim-in2}\n#el-anim .g-in3{animation-name:elAnim-in3}\n#el-anim .hl1{animation-name:elAnim-hl1}\n#el-anim .hl2{animation-name:elAnim-hl2}\n#el-anim .hl3{animation-name:elAnim-hl3}\n#el-anim .hl4{animation-name:elAnim-in3}\n#el-anim .hl5{animation-name:elAnim-in2}\n#el-anim .out1{animation-name:elAnim-out1}\n#el-anim .out2{animation-name:elAnim-out2}\n#el-anim .out3{animation-name:elAnim-out3}\n#el-anim .loop{animation-name:elAnim-loop}\n#el-anim .mlane{animation-name:elAnim-mlane}\n#el-anim .tlane{animation-name:elAnim-tlane}\n#el-anim .c1{animation-name:elAnim-c1}\n#el-anim .c2{animation-name:elAnim-c2}\n#el-anim .c3{animation-name:elAnim-c3}\n#el-anim .c4{animation-name:elAnim-c4}\n#el-anim .c5{animation-name:elAnim-c5}\n#el-anim .c6{animation-name:elAnim-c6}\n#el-anim .c7{animation-name:elAnim-c7}\n#el-anim .c8{animation-name:elAnim-c8}\n@keyframes elAnim-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}\n@keyframes elAnim-blink{0%{opacity:1}50%{opacity:0}}\n@keyframes elAnim-global{0%{opacity:0}2%,50%{opacity:1}52%,100%{opacity:0}}\n@keyframes elAnim-set{0%,8%{opacity:0}9%,16%{opacity:1}18%,100%{opacity:0}}\n@keyframes elAnim-timer{0%,12%{opacity:0}13%,18%{opacity:1}20%,100%{opacity:0}}\n@keyframes elAnim-cb1{0%,19%{opacity:0;transform:translate(258px,468px)}20%{opacity:1;transform:translate(258px,468px)}23%,78%{opacity:1;transform:translate(26px,468px)}82%,90%{opacity:1;transform:translate(26px,320px)}92%,100%{opacity:0;transform:translate(26px,320px)}}\n@keyframes elAnim-prom{0%,24%{opacity:0}25%,33%{opacity:1}35%,100%{opacity:0}}\n@keyframes elAnim-cb2{0%,30%{opacity:0;transform:translate(258px,398px)}31%{opacity:1;transform:translate(258px,398px)}34%,60%{opacity:1;transform:translate(26px,398px)}64%,72%{opacity:1;transform:translate(26px,320px)}74%,100%{opacity:0;transform:translate(26px,320px)}}\n@keyframes elAnim-log{0%,38%{opacity:0}39%,46%{opacity:1}48%,100%{opacity:0}}\n@keyframes elAnim-in2{0%,65%{opacity:0}66%,70%{opacity:1}72%,100%{opacity:0}}\n@keyframes elAnim-in3{0%,83%{opacity:0}84%,88%{opacity:1}90%,100%{opacity:0}}\n@keyframes elAnim-hl1{0%,8%{opacity:0}9%,16%{opacity:1}18%,100%{opacity:0}}\n@keyframes elAnim-hl2{0%,24%{opacity:0}25%,33%{opacity:1}35%,100%{opacity:0}}\n@keyframes elAnim-hl3{0%,38%{opacity:0}39%,46%{opacity:1}48%,100%{opacity:0}}\n@keyframes elAnim-out1{0%,42%{opacity:0}43%,97%{opacity:1}100%{opacity:0}}\n@keyframes elAnim-out2{0%,67%{opacity:0}68%,97%{opacity:1}100%{opacity:0}}\n@keyframes elAnim-out3{0%,85%{opacity:0}86%,97%{opacity:1}100%{opacity:0}}\n@keyframes elAnim-loop{0%,53%{color:#9a9a9a}55%,91%{color:#006400}93%,100%{color:#9a9a9a}}\n@keyframes elAnim-mlane{0%,55%{fill:#fafafa;stroke:#d0d0d0}56%,64%{fill:#eef6ee;stroke:#006400}66%,100%{fill:#fafafa;stroke:#d0d0d0}}\n@keyframes elAnim-tlane{0%,73%{fill:#fafafa;stroke:#d0d0d0}74%,82%{fill:#eef6ee;stroke:#006400}84%,100%{fill:#fafafa;stroke:#d0d0d0}}\n@keyframes elAnim-c1{0%,7%{opacity:1}8%,100%{opacity:0}}\n@keyframes elAnim-c2{0%,7%{opacity:0}8%,23%{opacity:1}24%,100%{opacity:0}}\n@keyframes elAnim-c3{0%,23%{opacity:0}24%,37%{opacity:1}38%,100%{opacity:0}}\n@keyframes elAnim-c4{0%,37%{opacity:0}38%,49%{opacity:1}50%,100%{opacity:0}}\n@keyframes elAnim-c5{0%,49%{opacity:0}50%,55%{opacity:1}56%,100%{opacity:0}}\n@keyframes elAnim-c6{0%,55%{opacity:0}56%,73%{opacity:1}74%,100%{opacity:0}}\n@keyframes elAnim-c7{0%,73%{opacity:0}74%,91%{opacity:1}92%,100%{opacity:0}}\n@keyframes elAnim-c8{0%,91%{opacity:0}92%,99%{opacity:1}100%{opacity:0}}\n@media (prefers-reduced-motion:reduce){#el-anim .anim,#el-anim .spin,#el-anim .caret{animation:none}#el-anim .controls{display:none}}\n\u003C\u002Fstyle>\n\u003Crect class=\"panel\" x=\"16\" y=\"16\" width=\"448\" height=\"140\" rx=\"10\"\u002F>\n\u003Crect class=\"hl hl1 anim\" x=\"20\" y=\"26\" width=\"440\" height=\"60\" rx=\"4\"\u002F>\n\u003Crect class=\"hl hl2 anim\" x=\"20\" y=\"86\" width=\"440\" height=\"40\" rx=\"4\"\u002F>\n\u003Crect class=\"hl hl3 anim\" x=\"20\" y=\"126\" width=\"440\" height=\"20\" rx=\"4\"\u002F>\n\u003Crect class=\"hl hl4 anim\" x=\"20\" y=\"46\" width=\"440\" height=\"20\" rx=\"4\"\u002F>\n\u003Crect class=\"hl hl5 anim\" x=\"20\" y=\"106\" width=\"440\" height=\"20\" rx=\"4\"\u002F>\n\u003Ctext class=\"mono code\" x=\"28\" y=\"40\">setTimeout(() =&gt; {\u003C\u002Ftext>\n\u003Ctext class=\"mono code\" x=\"42.4\" y=\"60\">console.log('hey, from set timeout');\u003C\u002Ftext>\n\u003Ctext class=\"mono code\" x=\"28\" y=\"80\">}, 0);\u003C\u002Ftext>\n\u003Ctext class=\"mono code\" x=\"28\" y=\"100\">new Promise((resolve) =&gt; resolve('ahoy from promise'))\u003C\u002Ftext>\n\u003Ctext class=\"mono code\" x=\"42.4\" y=\"120\">.then((v) =&gt; console.log(v));\u003C\u002Ftext>\n\u003Ctext class=\"mono code\" x=\"28\" y=\"140\">console.log('hello from console log');\u003C\u002Ftext>\n\u003Crect class=\"panel\" x=\"16\" y=\"170\" width=\"216\" height=\"190\" rx=\"10\"\u002F>\n\u003Ctext class=\"label\" x=\"28\" y=\"192\">Call stack\u003C\u002Ftext>\n\u003Crect class=\"panel\" x=\"248\" y=\"170\" width=\"216\" height=\"90\" rx=\"10\"\u002F>\n\u003Ctext class=\"label\" x=\"260\" y=\"192\">Web APIs\u003C\u002Ftext>\n\u003Crect class=\"panel\" x=\"248\" y=\"272\" width=\"216\" height=\"88\" rx=\"10\"\u002F>\n\u003Cg class=\"loop anim\">\n\u003Cg class=\"spin\">\n\u003Ccircle cx=\"276\" cy=\"318\" r=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-dasharray=\"90 23\"\u002F>\n\u003Cpolygon points=\"286.9,302.4 282.4,296.4 279.8,305.0\" fill=\"currentColor\"\u002F>\n\u003C\u002Fg>\n\u003Ctext x=\"306\" y=\"306\" style=\"font-size:18px;font-weight:600\" fill=\"currentColor\">Event Loop\u003C\u002Ftext>\n\u003C\u002Fg>\n\u003Ctext class=\"hint\" x=\"306\" y=\"325\">prázdný stack? → nejdřív\u003C\u002Ftext>\n\u003Ctext class=\"hint\" x=\"306\" y=\"340\">všechny microtasky, pak task\u003C\u002Ftext>\n\u003Crect class=\"panel mlane anim\" x=\"16\" y=\"376\" width=\"448\" height=\"58\" rx=\"10\"\u002F>\n\u003Ctext class=\"label\" x=\"28\" y=\"392\" style=\"font-size:15px\">Microtask queue\u003C\u002Ftext>\n\u003Ctext class=\"hint\" x=\"454\" y=\"392\" text-anchor=\"end\">Promise, queueMicrotask, MutationObserver\u003C\u002Ftext>\n\u003Crect class=\"panel tlane anim\" x=\"16\" y=\"446\" width=\"448\" height=\"58\" rx=\"10\"\u002F>\n\u003Ctext class=\"label\" x=\"28\" y=\"462\" style=\"font-size:15px\">Task queue\u003C\u002Ftext>\n\u003Ctext class=\"hint\" x=\"454\" y=\"462\" text-anchor=\"end\">setTimeout, události, síťové operace\u003C\u002Ftext>\n\u003Crect x=\"16\" y=\"520\" width=\"448\" height=\"144\" rx=\"8\" fill=\"#ffffff\" stroke=\"#cfd1d4\" stroke-width=\"1.5\"\u002F>\n\u003Cpath d=\"M16 546 V528 a8 8 0 0 1 8 -8 H456 a8 8 0 0 1 8 8 V546 Z\" fill=\"#f1f3f4\"\u002F>\n\u003Ctext class=\"ui\" x=\"28\" y=\"537\">Elements\u003C\u002Ftext>\n\u003Ctext class=\"ui\" x=\"90\" y=\"537\" style=\"fill:#202124\">Console\u003C\u002Ftext>\n\u003Ctext class=\"ui\" x=\"146\" y=\"537\">Sources\u003C\u002Ftext>\n\u003Ctext class=\"ui\" x=\"199\" y=\"537\">Network\u003C\u002Ftext>\n\u003Crect x=\"84\" y=\"543\" width=\"52\" height=\"3\" fill=\"#1a73e8\"\u002F>\n\u003Cline class=\"rule\" x1=\"16\" y1=\"546\" x2=\"464\" y2=\"546\"\u002F>\n\u003Ccircle cx=\"31\" cy=\"558\" r=\"5.5\" fill=\"none\" stroke=\"#5f6368\" stroke-width=\"1.2\"\u002F>\n\u003Cline x1=\"27.1\" y1=\"561.9\" x2=\"34.9\" y2=\"554.1\" stroke=\"#5f6368\" stroke-width=\"1.2\"\u002F>\n\u003Ctext class=\"ui\" x=\"46\" y=\"562\">top ▾\u003C\u002Ftext>\n\u003Crect x=\"84\" y=\"551\" width=\"150\" height=\"15\" rx=\"3\" fill=\"#ffffff\" stroke=\"#dadce0\"\u002F>\n\u003Ctext class=\"ui\" x=\"90\" y=\"562\" style=\"fill:#9aa0a6\">Filter\u003C\u002Ftext>\n\u003Ctext class=\"ui\" x=\"454\" y=\"562\" text-anchor=\"end\">Default levels ▾\u003C\u002Ftext>\n\u003Cline class=\"rule\" x1=\"16\" y1=\"570\" x2=\"464\" y2=\"570\"\u002F>\n\u003Cg class=\"out out1 anim\">\u003Ctext class=\"mono\" x=\"28\" y=\"584\">hello from console log\u003C\u002Ftext>\u003Ctext class=\"ui\" x=\"454\" y=\"584\" text-anchor=\"end\" style=\"text-decoration:underline\">script.js:6\u003C\u002Ftext>\u003Cline class=\"rule\" x1=\"16\" y1=\"590\" x2=\"464\" y2=\"590\"\u002F>\u003C\u002Fg>\n\u003Cg class=\"out out2 anim\">\u003Ctext class=\"mono\" x=\"28\" y=\"604\">ahoy from promise\u003C\u002Ftext>\u003Ctext class=\"ui\" x=\"454\" y=\"604\" text-anchor=\"end\" style=\"text-decoration:underline\">script.js:5\u003C\u002Ftext>\u003Cline class=\"rule\" x1=\"16\" y1=\"610\" x2=\"464\" y2=\"610\"\u002F>\u003C\u002Fg>\n\u003Cg class=\"out out3 anim\">\u003Ctext class=\"mono\" x=\"28\" y=\"624\">hey, from set timeout\u003C\u002Ftext>\u003Ctext class=\"ui\" x=\"454\" y=\"624\" text-anchor=\"end\" style=\"text-decoration:underline\">script.js:2\u003C\u002Ftext>\u003Cline class=\"rule\" x1=\"16\" y1=\"630\" x2=\"464\" y2=\"630\"\u002F>\u003C\u002Fg>\n\u003Ctext x=\"26\" y=\"646\" style=\"font-size:14px;font-weight:700\" fill=\"#1a73e8\">›\u003C\u002Ftext>\n\u003Crect class=\"caret\" x=\"38\" y=\"636\" width=\"1.2\" height=\"12\" fill=\"#202124\"\u002F>\n\u003Cg class=\"tok frame g-global anim\" transform=\"translate(26 320)\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">global execution context\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"tok frame g-set anim\" transform=\"translate(26 284)\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">setTimeout(…, 0)\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"tok timer g-timer anim\" transform=\"translate(258 208)\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">časovač: 0 ms\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"tok frame g-prom anim\" transform=\"translate(26 284)\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">new Promise(…).then(…)\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"tok frame g-log anim\" transform=\"translate(26 284)\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">console.log('hello…')\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"tok frame g-in2 anim\" transform=\"translate(26 284)\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">console.log(v)\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"tok frame g-in3 anim\" transform=\"translate(26 284)\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">console.log('hey…')\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"tok cb g-cb2 anim\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">(v) =&gt; console.log(v)\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"tok cb g-cb1 anim\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">() =&gt; { console.log(…) }\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c1 anim\">\u003Ctext x=\"16\" y=\"690\">1. Skript se spouští jako task. Do call stacku přijde\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">globální exekuční kontext.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c2 anim\">\u003Ctext x=\"16\" y=\"690\">2. setTimeout() předá časovač Web API. Po 0 ms\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">se jeho callback zařadí do Task queue.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c3 anim\">\u003Ctext x=\"16\" y=\"690\">3. Promise je hned splněná, takže callback z&#160;.then()\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">míří do Microtask queue.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c4 anim\">\u003Ctext x=\"16\" y=\"690\">4. console.log() proběhne synchronně přímo na call stacku\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">a&#160;vypíše do konzole první řádek.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c5 anim\">\u003Ctext x=\"16\" y=\"690\">5. Skript doběhl a&#160;call stack je prázdný.\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">Nastupuje Event Loop.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c6 anim\">\u003Ctext x=\"16\" y=\"690\">6. Event Loop nejdřív vyprázdní Microtask queue:\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">callback z&#160;.then() vypíše „ahoy from promise“.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c7 anim\">\u003Ctext x=\"16\" y=\"690\">7. Microtask queue je prázdná, Event Loop tedy vezme\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">první task z Task queue: callback ze setTimeout().\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap cap-end c8 anim\">\u003Ctext x=\"16\" y=\"690\">Výsledek: „hello“, „ahoy“, „hey“ – nejdřív synchronní\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">kód, pak všechny microtasky a&#160;teprve potom tasky.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"controls\">\n\u003Cg class=\"btn\" role=\"button\" tabindex=\"0\" aria-label=\"Přehrát animaci od začátku\" onclick=\"this.ownerSVGElement.getAnimations({subtree:true}).forEach(function(a){a.currentTime=0;a.play()})\" onkeydown=\"if(event.key==='Enter'||event.key===' '){event.preventDefault();this.onclick()}\">\u003Crect x=\"16\" y=\"731\" width=\"112\" height=\"24\" rx=\"12\"\u002F>\u003Crect class=\"ico\" x=\"27\" y=\"737\" width=\"2\" height=\"12\"\u002F>\u003Cpolygon class=\"ico\" points=\"37,737 37,749 30,743\"\u002F>\u003Ctext x=\"44\" y=\"747\">Od začátku\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"btn\" role=\"button\" tabindex=\"0\" aria-label=\"Zastavit animaci\" onclick=\"this.ownerSVGElement.getAnimations({subtree:true}).forEach(function(a){a.pause()})\" onkeydown=\"if(event.key==='Enter'||event.key===' '){event.preventDefault();this.onclick()}\">\u003Crect x=\"136\" y=\"731\" width=\"96\" height=\"24\" rx=\"12\"\u002F>\u003Crect class=\"ico\" x=\"147\" y=\"737\" width=\"3\" height=\"12\"\u002F>\u003Crect class=\"ico\" x=\"153\" y=\"737\" width=\"3\" height=\"12\"\u002F>\u003Ctext x=\"164\" y=\"747\">Zastavit\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"btn\" role=\"button\" tabindex=\"0\" aria-label=\"Pokračovat v animaci\" onclick=\"this.ownerSVGElement.getAnimations({subtree:true}).forEach(function(a){a.play()})\" onkeydown=\"if(event.key==='Enter'||event.key===' '){event.preventDefault();this.onclick()}\">\u003Crect x=\"240\" y=\"731\" width=\"112\" height=\"24\" rx=\"12\"\u002F>\u003Cpolygon class=\"ico\" points=\"251,737 251,749 260,743\"\u002F>\u003Ctext x=\"266\" y=\"747\">Pokračovat\u003C\u002Ftext>\u003C\u002Fg>\n\u003C\u002Fg>\n\u003C\u002Fsvg>\n",false,{"rendered":19,"protected":17},"",0,"closed","standard",{},[25],5,[],[28,32,36],{"id":29,"name":30,"slug":30,"taxonomy":31},10667210,"javascript","post_tag",{"id":33,"name":34,"slug":35,"taxonomy":31},10669219,"event loop","event-loop",{"id":37,"name":38,"slug":39,"taxonomy":31},10017291,"software engineering","software-engineering",{"en":41,"es":45,"fr":49},{"title":42,"slug":43,"description":44},"When We Say Event Loop","when-we-say-event-loop","How the JavaScript event loop decides the order of setTimeout, Promise and console.log: the engine, the call stack, Web APIs, the Task queue and the Microtask queue, step by step.",{"title":46,"slug":47,"description":48},"Cuando decimos Event Loop","cuando-decimos-event-loop","Cómo decide el event loop de JavaScript el orden de setTimeout, Promise y console.log: el engine, el call stack, las Web APIs, la Task queue y la Microtask queue, paso a paso.",{"title":50,"slug":51,"description":52},"Quand on dit Event Loop","quand-on-dit-event-loop","Comment l’event loop de JavaScript décide de l’ordre de setTimeout, Promise et console.log : l’engine, le call stack, les Web APIs, la Task queue et la Microtask queue, pas à pas.","\u003Cp>Le travail de développeur logiciel exige que nous apprenions des choses nouvelles et que nous suivions les tendances du métier. Que ce soit pour améliorer notre position sur le marché du travail ou simplement pour accroître nos connaissances et notre productivité. Au cours des deux dernières années, à ce que nous devons suivre se sont ajoutés d’abord les premiers signes de l’intelligence artificielle, sous la forme de divers assistants IA, de copilotes et d’intégrations de ChatGPT, capables de remplacer des processus courants et routiniers, mais aussi de générer des solutions à des problèmes plus complexes. Aujourd’hui, au moment où j’écris ce texte, les principaux modèles de langage sont capables de générer, à partir d’un seul prompt dans Claude Code Fable ou ChatGPT Astra, un projet entier qu’on peut déployer, tester et lancer. Du logiciel et de la médecine jusqu’au copywriting et à la traduction, les professionnels et les professionnelles de ces métiers font aujourd’hui face à des vagues de licenciements. De ce qu’on appelle le \u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTechnological_unemployment\">chômage technologique\u003C\u002Fa>, on ne parle plus seulement dans le milieu universitaire. Il est devenu un sujet et une réalité quotidienne des métiers en col blanc, voir \u003Ca href=\"https:\u002F\u002Fwww.technologyreview.com\u002F2024\u002F01\u002F27\u002F1087041\u002Ftechnological-unemployment-elon-musk-jobs-ai\u002F\">ici\u003C\u002Fa> ou \u003Ca href=\"https:\u002F\u002Fwww.ilo.org\u002Fresource\u002Farticle\u002Fminimizing-negative-effects-ai-induced-technological-unemployment\">ici\u003C\u002Fa>. Ce n’est ni la première ni la dernière fois que les progrès techniques rendent beaucoup d’emplois et de métiers inutiles et en créent, à l’inverse, de nouveaux. Dans ces cas-là, on \u003Ca href=\"https:\u002F\u002Fdictionary.cambridge.org\u002Fdictionary\u002Fenglish\u002Fseparate-the-wheat-from-the-chaff\">sépare le bon grain de l’ivraie\u003C\u002Fa>, et il est légitime de se demander : qui restera utile aux entreprises dans cette économie de marché AI-powered ?\u003C\u002Fp>\n\u003Cp>Les postes juniors semblent être la première cible, et la plus naturelle. Des connaissances superficielles, une capacité encore peu développée à voir les problèmes techniques dans le contexte plus large des exigences techniques, mais aussi business. L’IA cesse de souffrir de ces défauts, alors que les postes juniors sont justement connus pour eux.\u003C\u002Fp>\n\u003Cp>Si je regarde mon propre domaine, le front-end et les runtimes serveur du back-end node\u002Fdeno\u002Fbun, il me semble qu’une connaissance approfondie de l’Event Loop est l’une des rares « hard skills » qui, dans l’univers de Javascript, séparent un codeur junior d’un ingénieur logiciel senior. Qui comprend l’Event Loop peut répondre à plusieurs questions importantes, par exemple : comment se fait-il que Javascript soit un langage de programmation single-threaded et que nous y fassions pourtant plusieurs choses à la fois au moyen d’opérations asynchrones ? Sur l’Event Loop, on peut écrire bien des choses. Mes ambitions sont modestes. Je vais regarder l’Event Loop de manière à pouvoir expliquer l’une des questions les plus courantes lors d’un entretien d’embauche pour un poste de développeur front-end : dans quel ordre les opérations suivantes s’exécutent-elles, et pourquoi :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-js\">setTimeout(() =&gt; {\n  console.log('hey, from set timeout');\n}, 0);\n\nnew Promise((resolve) =&gt; resolve('ahoy from promise')).then((v) =&gt;\n  console.log(v)\n);\n\nconsole.log('hello from console log');\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2>Introduction technique au problème\u003C\u002Fh2>\n\u003Cp>Javascript est \u003Cstrong>single-threaded\u003C\u002Fstrong>. Si l’on ne compte pas la \u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWeb_Workers_API\">Web Workers API\u003C\u002Fa>, cela signifie que Javascript ne sait traiter qu’une seule opération à la fois. Javascript est conçu de telle sorte qu’il traite les opérations séquentiellement, selon la méthode LIFO (Last-In, First Out), et tant qu’une opération n’est pas terminée, elle bloque toutes les opérations suivantes. En temps normal, par exemple, après un clic sur un bouton, une seule requête GET lente vers une API distante bloquerait toute interaction de l’utilisateur ; toute l’interface du navigateur serait figée jusqu’à ce que la requête réseau (network request) aboutisse. Quand Brendan Eich a conçu Javascript dans les années quatre-vingt-dix, le traitement des opérations y était synchrone, avec des possibilités limitées de traiter les événements déclenchés par l’interaction de l’utilisateur avec le navigateur. Pourquoi Eich a conçu Javascript ainsi mériterait une enquête historique. Aujourd’hui, en tout cas, nous savons que les pages web fondées sur Javascript (c’est-à-dire tout le web) ne se comportent pas ainsi. L’utilisateur peut interagir avec plusieurs parties d’une page web à la fois. Comment un langage de programmation single-threaded peut-il y parvenir ?\u003C\u002Fp>\n\u003Ch2>Enter Javascript runtime\u003C\u002Fh2>\n\u003Cp>Quand on dit Javascript, nous devons nous rendre compte qu’il s’agit d’une \u003Cstrong>collection de bibliothèques et de composants\u003C\u002Fstrong>. Ce n’est qu’en les reliant que nous obtenons un tout fonctionnel – le \u003Cstrong>runtime\u003C\u002Fstrong>, c’est-à-dire l’environnement dans lequel tourne notre application. Aujourd’hui, nous avons le choix entre deux grands types de runtime. Il s’agit soit du runtime d’un navigateur web, soit du runtime de l’une des diverses implémentations serveur, par exemple Node.js, Deno ou le tout récent Bun. Bien que le runtime des navigateurs et celui des serveurs aient des éléments communs, on y trouve aussi des différences importantes. Elles découlent de la vocation de chaque type de runtime. Les navigateurs web sont orientés vers le travail avec le DOM (\u003Ccode>window\u003C\u002Fcode>, \u003Ccode>document\u003C\u002Fcode>), les interactions de l’utilisateur (\u003Ccode>addEventListener\u003C\u002Fcode>), les opérations réseau au moyen de \u003Ccode>fetch\u003C\u002Fcode> ou \u003Ccode>XMLHttpRequest\u003C\u002Fcode>, le stockage local dans le navigateur (\u003Ccode>localStorage\u003C\u002Fcode>, \u003Ccode>sessionStorage\u003C\u002Fcode>), etc. Le runtime de Node.js ou de Bun, lui, est orienté vers l’environnement des applications serveur, le travail avec le système de fichiers, les bases de données, ou les opérations low-level sur le système d’exploitation. \u003Cstrong>Mais les deux types de runtime implémentent l’Event Loop\u003C\u002Fstrong>. Pour simplifier, je ne m’intéresserai dans la suite qu’au runtime des navigateurs web.\u003C\u002Fp>\n\u003Ch2>Le runtime des navigateurs web\u003C\u002Fh2>\n\u003Cp>Le runtime Javascript des navigateurs web se compose des parties suivantes :\u003C\u002Fp>\n\u003Cul>\n\u003Cli>Javascript Engine\u003C\u002Fli>\n\u003Cli>Web APIs\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Event Loop\u003C\u002Fstrong>\u003C\u002Fli>\n\u003Cli>Task queue et Microtask queue\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>L’Event Loop n’est que l’un des composants et n’entre en jeu que lors du traitement de code asynchrone. Si notre programme Javascript n’utilise que des opérations synchrones, l’Event Loop ne sera jamais sollicité. L’Event Loop compte pourtant parmi les caractéristiques majeures de Javascript, et c’est grâce à lui que les applications web modernes d’aujourd’hui sont pleines de fonctionnalités asynchrones. Pour comprendre l’Event Loop et, plus généralement, le traitement du code asynchrone en Javascript, il est bon de se rappeler \u003Cstrong>comment Javascript se comporte lors d’opérations entièrement synchrones\u003C\u002Fstrong>. Pour cela, il est bon de comprendre ensuite le fonctionnement du Javascript engine.\u003C\u002Fp>\n\u003Ch3>Javascript engine\u003C\u002Fh3>\n\u003Cp>Le cœur symbolique de Javascript, c’est l’engine. Les fonctions principales d’un JavaScript engine sont le parsing, l’interprétation et l’exécution du code. Par parsing du code, nous entendons \u003Cstrong>la conversion du code source JavaScript en ce qu’on appelle un arbre syntaxique abstrait (AST)\u003C\u002Fstrong>, une structure que l’engine est capable de traiter. Par interprétation, nous entendons le fait que \u003Cstrong>la plupart des engines modernes interprètent d’abord le code, puis le compilent en code machine (just-in-time compilation – JIT)\u003C\u002Fstrong>. Par exécution du code, enfin, nous entendons qu’\u003Cstrong>après la traduction en code machine, l’engine commence à exécuter les instructions et assure le fonctionnement de l’application\u003C\u002Fstrong>.\u003C\u002Fp>\n\u003Cp>Exemples de JavaScript engines populaires :\u003C\u002Fp>\n\u003Cul>\n\u003Cli>V8 : utilisé par Google Chrome et Node.js. Il est connu pour sa rapidité et son efficacité.\u003C\u002Fli>\n\u003Cli>SpiderMonkey : le premier JavaScript engine, développé par la société Netscape, aujourd’hui utilisé dans Firefox.\u003C\u002Fli>\n\u003Cli>JavaScriptCore (aka Nitro) : utilisé par Safari et d’autres produits d’Apple.\u003C\u002Fli>\n\u003Cli>Chakra : utilisé autrefois par Microsoft Edge (legacy), aujourd’hui remplacé par l’engine V8 dans le nouvel Edge (Chromium).\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Le JavaScript engine fournit en outre deux structures de mémoire : le \u003Cstrong>heap\u003C\u002Fstrong> et le \u003Cstrong>call stack\u003C\u002Fstrong>. Leurs différences et leur fonctionnement mériteraient, eux aussi, un article à part sur le Memory management en Javascript. Pour une définition de base, lisez Wikipédia ou obtenez-la d’un prompt à ChatGPT.\u003C\u002Fp>\n\u003Cp>Ce qui est important pour comprendre l’Event Loop, c’est que \u003Cstrong>le Javascript Engine en tant que tel n’implémente pas le traitement du code asynchrone\u003C\u002Fstrong>. Toute l’asynchronie de Javascript doit être implémentée ailleurs. Où donc ? C’est justement l’affaire du Javascript Runtime, qui ajoute au Javascript engine les Web APIs (nous sommes dans le navigateur), l’Event loop et les files d’attente Task queue et Microtask queue. Fun fact : si Javascript était un langage de programmation purement synchrone, il se contenterait du heap et du call stack et n’aurait pas besoin d’Event Loop. Mais j’anticipe.\u003C\u002Fp>\n\u003Ch3>Heap\u003C\u002Fh3>\n\u003Cp>Le \u003Cstrong>heap\u003C\u002Fstrong>, pour ce qui est de comprendre l’Event Loop, n’est pas si important. Le heap est une structure de mémoire où le Javascript Engine stocke les types de données complexes (Object, Array, Function, Map, Set, WeakMap, WeakSet). La taille du heap est donnée par la taille de la mémoire RAM. Contrairement au call stack, le heap n’est pas organisé de manière séquentielle : c’est une mémoire non linéaire. Dans le heap sont stockées des données dont le type et la taille ne sont pas connus à la compilation et peuvent changer pendant le runtime (affectation d’autres valeurs aux properties des objets, apparition de nouvelles properties, de nouveaux objets, etc.) ; le heap est donc une mémoire dynamique. Techniquement parlant, le heap est une implémentation du patron de conception courant de l’allocation dynamique de la mémoire (dynamic memory allocation pattern), ce qui signifie qu’il exige un Garbage Collector, qui nettoie la mémoire allouée pendant le runtime selon qu’une partie du programme a encore besoin (a des références dans le call stack) des objets alloués dans le heap. Si ce n’est pas le cas, le Garbage Collector, en temps normal, nettoie le heap et libère la mémoire. Le Garbage Collector n’est cependant pas précis à 100 %, si bien que, malgré son existence, des fuites de mémoire (memory leaks) peuvent toujours se produire.\u003C\u002Fp>\n\u003Ch3>Call Stack\u003C\u002Fh3>\n\u003Cp>D’un autre côté, le \u003Cstrong>call stack\u003C\u002Fstrong> est une mémoire statique plus simple, et bien plus intéressante pour expliquer l’Event Loop, car elle a à voir avec l’ordre dans lequel le code s’exécute en Javascript. Le Javascript engine travaille avec le call stack de manière uniquement synchrone, pas à pas. Sur le plan technique, le call stack est une implémentation du \u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FStack_(abstract_data_type)\">patron de conception classique de la Pile (Stack)\u003C\u002Fa>, où le dernier élément ajouté en mémoire est traité le premier (Last In, First Out, LIFO). Et qu’est-ce qui est exactement stocké dans le call stack, et de \u003Cstrong>quoi\u003C\u002Fstrong> détermine-t-il l’ordre au juste ? Pour simplifier, on peut se représenter un programme Javascript comme une collection de fonctions et de variables, et le call stack veille à ce que nos fonctions soient appelées et nos variables déclarées dans le bon ordre et au bon moment.\u003C\u002Fp>\n\u003Cp>Alors que les types de données complexes (objets, tableaux, etc.) sont stockés dans le heap, le call stack ne contient que des références\u002Fpointeurs vers le heap. Ce qui, à l’inverse, est réellement stocké dans le call stack, ce sont les valeurs des types de données primitifs (string, integer, boolean, etc.), mais aussi tout le \u003Cstrong>contexte d’exécution\u003C\u002Fstrong> d’une fonction, par quoi l’on entend tout « ce qui est nécessaire pour appeler la fonction ». Là encore, il conviendrait d’approfondir tout ce que recouvre le contexte d’exécution d’une fonction, mais, pour simplifier, le contexte d’exécution comprend :\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Variable Environment (environnement des variables)\u003C\u002Fstrong> : un enregistrement des variables et des déclarations de fonctions propres à la fonction donnée.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Lexical Environment (environnement lexical)\u003C\u002Fstrong> : des références au scope parent (environnement lexical) et les variables définies avec let et const.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>La liaison de this\u003C\u002Fstrong> : la valeur de this dans la fonction en cours.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Les détails propres à l’appel\u003C\u002Fstrong> : les arguments passés à la fonction et le mécanisme de son retour (c’est-à-dire l’endroit du programme où celui-ci doit revenir une fois la fonction terminée).\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>Si je laisse Claude générer un exemple au hasard pour montrer comment fonctionne le call stack, prenons par exemple le code suivant :\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-js\">function multiply(x, y) {\n  const result = x * y;\n  return result;\n}\n\nfunction calculate() {\n  const a = 10;\n  const b = 20;\n  const product = multiply(a, b);\n  console.log(product);\n}\n\ncalculate();\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Que se passe-t-il exactement dans un exemple aussi simple ? La situation est la suivante : dans le \u003Cstrong>call stack\u003C\u002Fstrong> est d’abord placée la fonction \u003Ccode>calculate()\u003C\u002Fcode>, ou plutôt tout son contexte d’exécution. Ensuite, le contexte d’exécution de la fonction \u003Ccode>multiply()\u003C\u002Fcode> est ajouté au \u003Cstrong>call stack\u003C\u002Fstrong>. Dès que la fonction \u003Ccode>multiply()\u003C\u002Fcode> a été appelée et son résultat affecté à la variable \u003Ccode>product\u003C\u002Fcode>, la fonction \u003Ccode>multiply()\u003C\u002Fcode> et son contexte d’exécution sont retirés du call stack. Puis le contexte d’exécution de \u003Ccode>console.log()\u003C\u002Fcode> est placé dans le call stack. Après l’affichage de la valeur « 200 » dans la console du navigateur, le contexte d’exécution de \u003Ccode>console.log()\u003C\u002Fcode> est retiré du call stack. Enfin, le contexte d’exécution de la fonction \u003Ccode>calculate()\u003C\u002Fcode> est retiré lui aussi.\u003C\u002Fp>\n\u003Cp>Remarquons que l’exemple ne contient pas de variables globales et que l’appel de la fonction calculate() ne se trouve, en apparence, dans aucune fonction dont le contexte d’exécution pourrait être ajouté au call stack. Comment, alors, les variables globales et les appels de fonctions dans le scope global parviennent-ils dans le call stack ?\u003C\u002Fp>\n\u003Ch4>Global Execution Context (GEC)\u003C\u002Fh4>\n\u003Cp>Tout ce qui se trouve dans le scope global peut être compris comme appelé à l’intérieur d’une fonction principale, dont le contexte d’exécution a un nom et un comportement particuliers : le \u003Cstrong>global execution context\u003C\u002Fstrong>. Modifions l’exemple utilisé plus haut et ajoutons-y des variables globales.\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-js\">var globalA = 'testGlobalA';\nconst globalB = 'testGlobalB';\n\nfunction multiply(x, y) {\n  const result = x * y;\n  return result;\n}\n\nfunction calculate() {\n  const a = 10;\n  const b = 20;\n  const product = multiply(a, b);\n  console.log(product);\n}\n\ncalculate();\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Si nous admettons que le contexte global de nos scripts est traité comme une sorte de fonction « main », notre script se comportera de la manière suivante : le contexte d’exécution global est ajouté au call stack, la variable « globalA » est ajoutée à l’\u003Cstrong>environnement des variables\u003C\u002Fstrong>, la variable « globalB » est ajoutée à l’\u003Cstrong>environnement lexical\u003C\u002Fstrong>, et une \u003Cstrong>liaison de this\u003C\u002Fstrong> est créée, qui, dans l’environnement du navigateur, renverra à l’objet global \u003Cstrong>window\u003C\u002Fstrong>. En ce qui concerne l’objet \u003Cstrong>window\u003C\u002Fstrong>, il est bon de rappeler que, si \u003Cstrong>var globalA\u003C\u002Fstrong> est ajoutée comme property de l’objet \u003Cstrong>window\u003C\u002Fstrong>, les variables définies avec \u003Cstrong>const\u003C\u002Fstrong> ou \u003Cstrong>let\u003C\u002Fstrong> sont ajoutées au scope global\u002Fà l’environnement lexical global, mais non à l’objet global \u003Cstrong>window\u003C\u002Fstrong>.\u003C\u002Fp>\n\u003Cp>Dès que toutes les fonctions définies « à l’intérieur » du contexte d’exécution global ont été ajoutées au call stack, traitées et retirées du call stack, il ne reste dans le call stack que le contexte d’exécution global, en tant que dernière fonction, et celle-ci est finalement retirée à son tour du call stack, qui se retrouve vide.\u003C\u002Fp>\n\u003Cp>Voilà du moins comment cela fonctionne en principe, si tout le code que traite le Javascript engine est \u003Cstrong>synchrone\u003C\u002Fstrong>. Si notre application (lisez : à l’intérieur du contexte d’exécution global) contient des fonctions traitées de manière asynchrone, la situation est sensiblement différente.\u003C\u002Fp>\n\u003Cp>Nous avons dit que le \u003Cstrong>global execution context\u003C\u002Fstrong> (GEC) est une sorte de fonction « main » initiale et le point d’entrée de l’application. Il semble qu’il doive tourner tout le temps, tant que tout le code n’a pas été traité. Mais qu’en est-il des parties asynchrones du code ? Elles peuvent bien lancer d’autres fonctions, qui renvoient de nouvelles valeurs et créent de nouveaux contextes d’exécution, si bien que le contexte d’exécution global doit être « d’une manière ou d’une autre » présent dans le call stack. Et comment et quand le code asynchrone est-il appelé, lorsque le call stack est partiellement rempli, ou lorsqu’il est entièrement vide ? Et si le call stack est vide, comment est-il possible que quelque chose se lance en Javascript, par exemple quand l’utilisateur clique sur un bouton et que se déclenche le callback d’un event listener enregistré au moyen d’addEventListener() ? Et qu’est-ce qui surveille les callbacks asynchrones d’addEventListener, alors que le Javascript engine est entièrement synchrone, et qu’il ne contient même aucune fonction addEventListener ?\u003C\u002Fp>\n\u003Ch2>L’asynchronie en Javascript, ou Web APIs, Task queues, Event loop\u003C\u002Fh2>\n\u003Cp>L’engine du langage de programmation Javascript ne contient ni l’objet \u003Ccode>console\u003C\u002Fcode> ni sa méthode \u003Ccode>console.log()\u003C\u002Fcode>. Ce qui peut surprendre. Le fait est que des fonctionnalités courantes pour les programmeurs, comme \u003Ccode>setTimeout\u003C\u002Fcode>, \u003Ccode>addEventListener\u003C\u002Fcode>, les opérations de manipulation du \u003Ccode>DOM\u003C\u002Fcode>, les requêtes réseau asynchrones \u003Ccode>fetch\u003C\u002Fcode> ou \u003Ccode>XMLHttpRequest\u003C\u002Fcode>, font partie non pas du Javascript engine, mais d’un autre composant du Javascript runtime : la Web API.\u003C\u002Fp>\n\u003Ch3>Web API\u003C\u002Fh3>\n\u003Cp>Comment exactement le Javascript engine parvient-il à coopérer avec la Web API de façon à avoir accès aux fonctions \u003Ccode>console.log()\u003C\u002Fcode> ou \u003Ccode>setTimeout()\u003C\u002Fcode>, si elles ne font pas directement partie de l’engine ? Le Javascript Engine (par ex. V8) possède un \u003Cstrong>registre interne des fonctions natives\u003C\u002Fstrong>, définies dans la spécification ECMAScript (par ex. Math.random(), Array.prototype.map(), Date.now()). C’est ainsi que l’engine reconnaît que \u003Ccode>setTimeout()\u003C\u002Fcode> n’est pas une fonction native ; il va donc chercher dans le scope global, ou plus précisément dans l’objet \u003Ccode>window\u003C\u002Fcode>, et tente d’appeler la méthode \u003Ccode>window.setTimeout()\u003C\u002Fcode>. Le Javascript runtime veille en effet, avant même l’initialisation du programme, à ce que le contexte d’exécution global (et donc l’environnement lexical de chaque fonction, voir plus haut) contienne les objets qui font partie de la Web API, par ex. document, navigator, performance, Intl, crypto, console.\u003C\u002Fp>\n\u003Cp>Quand j’ai demandé à ChatGPT de m’expliquer le processus d’exécution de la méthode \u003Ccode>console.log()\u003C\u002Fcode>, il m’a écrit, à tort, que \u003Ccode>console.log()\u003C\u002Fcode> fait partie du Javascript engine lui-même. Quand je lui ai fait remarquer que ce n’était pas le cas, ChatGPT s’est excusé et m’a proposé une réponse plus exacte. Je voulais savoir pourquoi il avait fait cette erreur. Il l’a mise sur le compte d’un modèle mental du fonctionnement de Javascript, prétendument répandu parmi les développeurs (et donc aussi parmi les blogueurs), selon lequel \u003Ccode>console.log()\u003C\u002Fcode> est une opération synchrone, donc traitée directement sur le call stack (ce qui est vrai), d’où il découlerait que \u003Cstrong>si quelque chose est traité directement sur le call stack (et n’a pas besoin de callbacks traités de manière asynchrone en dehors du thread principal de Javascript, ni donc de l’Event loop), cela doit faire partie du Javascript engine.\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>C’est une erreur. La méthode \u003Ccode>console.log()\u003C\u002Fcode>n’existe pas dans le Javascript engine, celui-ci n’en sait rien, sinon qu’elle n’est pas une fonction native. Il en délègue le traitement concret au Javascript runtime, où le navigateur (ou nodejs) exécute ensuite la fonction au moyen de code bas niveau en langage C++, via des bindings C++, c’est-à-dire des liaisons entre une fonction Javascript et une fonction C++ qui accomplit réellement ce que nous demandons à la fonction Javascript.\u003C\u002Fp>\n\u003Cp>Nous savons maintenant, en gros, comment cela fonctionne quand le Javascript engine doit appeler une fonction non native de la Web API. Mais qu’en est-il de l’asynchronie et de l’appel de \u003Ccode>setTimeout()\u003C\u002Fcode>, \u003Ccode>addEventListener()\u003C\u002Fcode> ou \u003Ccode>fetch()\u003C\u002Fcode> ?\u003C\u002Fp>\n\u003Ch3>Callbacks et Task Queues en général\u003C\u002Fh3>\n\u003Cp>En tant que langage single-threaded, le Javascript engine doit déléguer l’exécution des opérations asynchrones hors de lui-même. Outre la Web API, le Javascript runtime contient d’autres composants qui rendent les opérations asynchrones possibles : les Task queues et l’Event loop. Toutes les opérations asynchrones en Javascript sont réalisées au moyen de callbacks (y compris \u003Ccode>await\u002Fasync\u003C\u002Fcode>, qui sont du sugar code reposant sur l’asynchronie fondée sur les Promises). Lorsque l’engine rencontre une opération asynchrone, il en confie entièrement le traitement à la Web API. S’il s’agit d’un minuteur comme \u003Ccode>setTimeout()\u003C\u002Fcode>, la Web API attend le nombre de millisecondes indiqué ; s’il s’agit d’une requête réseau comme \u003Ccode>fetch\u003C\u002Fcode>, c’est encore à la Web API d’attendre la réponse à cette requête. Dès que la Web API a terminé le traitement de l’opération asynchrone elle-même, elle prend le \u003Cstrong>callback\u003C\u002Fstrong> défini par le programmeur et transmet cette fonction de callback à un composant particulier du Javascript runtime, une file appelée \u003Cstrong>Task queue\u003C\u002Fstrong> ou \u003Cstrong>Microtask\u003C\u002Fstrong> \u003Cstrong>queue\u003C\u002Fstrong>.\u003C\u002Fp>\n\u003Cp>Les callbacks restent dans les files tant que le call stack n’est pas entièrement vide, ou plus précisément tant qu’il n’y reste pas uniquement le contexte d’exécution global (GEC). Dès qu’il n’y a plus que le GEC dans le call stack, c’est au tour d’un autre composant : l’Event Loop.\u003C\u002Fp>\n\u003Ch3>Task queue\u003C\u002Fh3>\n\u003Cp>La Task queue est la plus ancienne des deux files et, en un sens, elle est aussi ancienne que Javascript lui-même. Dès les premières versions de Javascript dans Netscape Navigator, on disposait de \u003Ccode>setTimeout()\u003C\u002Fcode> et de la réaction aux événements déclenchés par l’utilisateur, et l’un comme l’autre avaient besoin d’un endroit où le callback attend que l’engine ait terminé ce qu’il est en train de faire. Formellement, cependant, la Task queue (et l’Event loop en général) n’a été décrite que par la spécification HTML5 de l’organisation WHATWG, qui a unifié la manière dont tous les navigateurs doivent se comporter à cet égard.\u003C\u002Fp>\n\u003Cp>La raison d’être de la Task queue est celle par laquelle nous avons commencé : un engine single-threaded ne doit rien attendre. La Web API gère donc l’attente hors de l’engine et ne transmet à la Task queue que le callback prêt, qui sera traité quand son tour viendra. Dans la Task queue (la spécification HTML les appelle \u003Cstrong>tasks\u003C\u002Fstrong> ; dans les blogs, on rencontre souvent aussi le terme \u003Cstrong>macrotasks\u003C\u002Fstrong>) se trouvent :\u003C\u002Fp>\n\u003Cul>\n\u003Cli>les callbacks des minuteurs \u003Ccode>setTimeout()\u003C\u002Fcode> et \u003Ccode>setInterval()\u003C\u002Fcode>,\u003C\u002Fli>\n\u003Cli>les callbacks des événements enregistrés au moyen d’\u003Ccode>addEventListener()\u003C\u002Fcode> (clic, frappe d’une touche, scroll…),\u003C\u002Fli>\n\u003Cli>les événements réseau des anciennes API, par exemple \u003Ccode>onload\u003C\u002Fcode> de \u003Ccode>XMLHttpRequest\u003C\u002Fcode> ou les messages de \u003Ccode>WebSocket\u003C\u002Fcode>,\u003C\u002Fli>\n\u003Cli>les messages entre fenêtres et workers envoyés au moyen de \u003Ccode>postMessage()\u003C\u002Fcode>,\u003C\u002Fli>\n\u003Cli>et, d’une manière générale, le lancement même du script depuis la balise \u003Ccode>&lt;script&gt;\u003C\u002Fcode> – notre contexte d’exécution global commence donc lui aussi sa vie comme une task.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Deux choses méritent d’être mentionnées. Premièrement, « Task queue » au singulier est une simplification. La spécification autorise le navigateur à avoir plusieurs files (par ex. une pour les interactions de l’utilisateur et une autre pour les minuteurs) et à choisir entre elles, si bien que le navigateur peut donner la priorité à un clic sur un minuteur. Au sein d’une même file, en revanche, c’est la méthode FIFO qui s’applique. Deuxièmement, \u003Cstrong>l’Event loop ne prend jamais qu’une seule task dans la Task queue\u003C\u002Fstrong>, et entre deux tasks, le navigateur a l’occasion de redessiner la page. C’est pourquoi même \u003Ccode>setTimeout(callback, 0)\u003C\u002Fcode> ne signifie pas « tout de suite », mais « au plus tôt au prochain tour de l’Event loop ». De plus, lors d’appels de minuteurs profondément imbriqués, les navigateurs portent le délai minimal à 4 ms.\u003C\u002Fp>\n\u003Ch3>Microtask queue\u003C\u002Fh3>\n\u003Cp>La Microtask queue est bien plus jeune. La notion de \u003Cstrong>microtask\u003C\u002Fstrong> est apparue vers 2011, lorsque des ingénieurs de Mozilla et de Chromium proposaient un remplacement de ce qu’on appelait les Mutation Events – un ancien mécanisme qui permettait de suivre les changements dans le DOM. Les Mutation Events se déclenchaient de manière synchrone à chaque changement du DOM, ce qui était lent et sujet aux erreurs. La nouvelle API \u003Ccode>MutationObserver\u003C\u002Fcode> devait livrer les changements de manière asynchrone et tous ensemble, en un lot, mais en même temps avant que le navigateur ne redessine la page ou ne traite l’événement suivant. La Task queue n’y suffisait pas, car entre deux tasks, un redessin ou le traitement d’un autre clic peut déjà avoir lieu. C’est pourquoi est née une nouvelle file, dont les callbacks sont traités \u003Cstrong>dès que le call stack se vide, avant même la task suivante\u003C\u002Fstrong>.\u003C\u002Fp>\n\u003Cp>Le deuxième usage de la Microtask queue, aujourd’hui le plus connu, ce sont les \u003Cstrong>Promises\u003C\u002Fstrong>, standardisées dans ECMAScript 2015 (ES6). La spécification ECMAScript les appelle « jobs », mais le navigateur les place justement dans la Microtask queue. Les Promises ont ainsi obtenu une garantie importante : le callback de \u003Ccode>.then()\u003C\u002Fcode> n’est jamais appelé de manière synchrone, mais jamais inutilement tard non plus – toujours juste après la fin du code en cours d’exécution. Vers 2019, les navigateurs ont ensuite ajouté la fonction \u003Ccode>queueMicrotask()\u003C\u002Fcode>, grâce à laquelle le programmeur peut lui aussi planifier directement une microtask, sans passer par une Promise.\u003C\u002Fp>\n\u003Cp>Dans la Microtask queue se trouvent :\u003C\u002Fp>\n\u003Cul>\n\u003Cli>les callbacks des Promises : \u003Ccode>.then()\u003C\u002Fcode>, \u003Ccode>.catch()\u003C\u002Fcode>, \u003Ccode>.finally()\u003C\u002Fcode>,\u003C\u002Fli>\n\u003Cli>la suite d’une fonction async après \u003Ccode>await\u003C\u002Fcode> (async\u002Fawait est construit sur les Promises),\u003C\u002Fli>\n\u003Cli>les callbacks passés à \u003Ccode>queueMicrotask()\u003C\u002Fcode>,\u003C\u002Fli>\n\u003Cli>les callbacks de \u003Ccode>MutationObserver\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Et encore une règle importante : alors que l’Event loop ne prend dans la Task queue qu’une seule task à un moment donné, \u003Cstrong>il vide toujours la Microtask queue tout entière\u003C\u002Fstrong>, y compris les microtasks qui s’y sont ajoutées pendant son traitement. Si une microtask ajoutait sans cesse une autre microtask, le navigateur n’arriverait plus ni au clic ni au redessin de la page, et la page se figerait – exactement comme lors d’une boucle infinie dans du code synchrone.\u003C\u002Fp>\n\u003Ch2>Event Loop\u003C\u002Fh2>\n\u003Cp>Le mystérieux Event loop va maintenant se révéler comme quelque chose de relativement trivial. L’Event loop n’est rien d’autre qu’un composant du Javascript runtime qui vérifie sans cesse si le call stack est plein. S’il est vide, la tâche suivante de l’Event loop est de décider s’il faut prendre, selon la méthode FIFO, le premier callback de la Task queue ou de la Microtask queue. Voilà tout.\u003C\u002Fp>\n\u003Csvg id=\"el-anim\" xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" viewBox=\"0 0 480 764\" role=\"img\" aria-label=\"Animation : comment l’Event Loop traite l’exemple initial\" aria-describedby=\"el-anim-desc\" style=\"display:block;width:100%;max-width:560px;height:auto;margin:2.5rem auto\">\n\u003Cdesc id=\"el-anim-desc\">Le script s’exécute dans le contexte d’exécution global. setTimeout confie un minuteur à la Web API et, après 0 ms, son callback est placé dans la Task queue. Le callback de .then() de la Promise tenue est placé dans la Microtask queue. console.log s’exécute de façon synchrone et écrit dans la console « hello from console log ». Quand le call stack se vide, l’Event Loop traite d’abord la Microtask queue et écrit « ahoy from promise », et ce n’est qu’ensuite qu’il prend la task de la Task queue et écrit « hey, from set timeout ».\u003C\u002Fdesc>\n\u003Cstyle>\n#el-anim{font-family:\"EB Garamond\",Georgia,serif}\n#el-anim .mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11px;fill:#1a1a1a}\n#el-anim .code{font-size:12px}\n#el-anim .ui{font-family:system-ui,-apple-system,\"Segoe UI\",Roboto,sans-serif;font-size:11px;fill:#5f6368}\n#el-anim .panel{fill:#fafafa;stroke:#d0d0d0;stroke-width:1.5}\n#el-anim .label{font-size:16px;font-weight:600;fill:#1a1a1a}\n#el-anim .hint{font-size:12px;fill:#8a8a8a}\n#el-anim .cap text{font-size:15px;fill:#1a1a1a}\n#el-anim .frame rect{fill:#eeeeee;stroke:#8a8a8a;stroke-width:1.2}\n#el-anim .cb rect{fill:#e6f0e6;stroke:#006400;stroke-width:1.2}\n#el-anim .timer rect{fill:#fff6db;stroke:#b8860b;stroke-width:1.2}\n#el-anim .hl{fill:#fff1a8}\n#el-anim .rule{stroke:#e8eaed;stroke-width:1}\n#el-anim .anim{animation-duration:30s;animation-iteration-count:infinite;animation-timing-function:ease-in-out;animation-fill-mode:both}\n#el-anim .btn{cursor:pointer}\n#el-anim .btn:focus{outline:none}\n#el-anim .btn rect{fill:#ffffff;stroke:#c8c8c8;stroke-width:1.2}\n#el-anim .btn:hover rect,#el-anim .btn:focus-visible rect{fill:#eef6ee;stroke:#006400}\n#el-anim .btn text{font-size:14px;fill:#1a1a1a}\n#el-anim .btn .ico{fill:#1a1a1a}\n#el-anim .tok,#el-anim .hl,#el-anim .cap{opacity:0}\n#el-anim .out,#el-anim .cap-end{opacity:1}\n#el-anim .loop{color:#9a9a9a}\n#el-anim .spin{transform-box:fill-box;transform-origin:center;animation:elAnim-spin 3s linear infinite}\n#el-anim .caret{animation:elAnim-blink 1.1s steps(1) infinite}\n#el-anim .g-global{animation-name:elAnim-global}\n#el-anim .g-set{animation-name:elAnim-set}\n#el-anim .g-timer{animation-name:elAnim-timer}\n#el-anim .g-cb1{animation-name:elAnim-cb1}\n#el-anim .g-prom{animation-name:elAnim-prom}\n#el-anim .g-cb2{animation-name:elAnim-cb2}\n#el-anim .g-log{animation-name:elAnim-log}\n#el-anim .g-in2{animation-name:elAnim-in2}\n#el-anim .g-in3{animation-name:elAnim-in3}\n#el-anim .hl1{animation-name:elAnim-hl1}\n#el-anim .hl2{animation-name:elAnim-hl2}\n#el-anim .hl3{animation-name:elAnim-hl3}\n#el-anim .hl4{animation-name:elAnim-in3}\n#el-anim .hl5{animation-name:elAnim-in2}\n#el-anim .out1{animation-name:elAnim-out1}\n#el-anim .out2{animation-name:elAnim-out2}\n#el-anim .out3{animation-name:elAnim-out3}\n#el-anim .loop{animation-name:elAnim-loop}\n#el-anim .mlane{animation-name:elAnim-mlane}\n#el-anim .tlane{animation-name:elAnim-tlane}\n#el-anim .c1{animation-name:elAnim-c1}\n#el-anim .c2{animation-name:elAnim-c2}\n#el-anim .c3{animation-name:elAnim-c3}\n#el-anim .c4{animation-name:elAnim-c4}\n#el-anim .c5{animation-name:elAnim-c5}\n#el-anim .c6{animation-name:elAnim-c6}\n#el-anim .c7{animation-name:elAnim-c7}\n#el-anim .c8{animation-name:elAnim-c8}\n@keyframes elAnim-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}\n@keyframes elAnim-blink{0%{opacity:1}50%{opacity:0}}\n@keyframes elAnim-global{0%{opacity:0}2%,50%{opacity:1}52%,100%{opacity:0}}\n@keyframes elAnim-set{0%,8%{opacity:0}9%,16%{opacity:1}18%,100%{opacity:0}}\n@keyframes elAnim-timer{0%,12%{opacity:0}13%,18%{opacity:1}20%,100%{opacity:0}}\n@keyframes elAnim-cb1{0%,19%{opacity:0;transform:translate(258px,468px)}20%{opacity:1;transform:translate(258px,468px)}23%,78%{opacity:1;transform:translate(26px,468px)}82%,90%{opacity:1;transform:translate(26px,320px)}92%,100%{opacity:0;transform:translate(26px,320px)}}\n@keyframes elAnim-prom{0%,24%{opacity:0}25%,33%{opacity:1}35%,100%{opacity:0}}\n@keyframes elAnim-cb2{0%,30%{opacity:0;transform:translate(258px,398px)}31%{opacity:1;transform:translate(258px,398px)}34%,60%{opacity:1;transform:translate(26px,398px)}64%,72%{opacity:1;transform:translate(26px,320px)}74%,100%{opacity:0;transform:translate(26px,320px)}}\n@keyframes elAnim-log{0%,38%{opacity:0}39%,46%{opacity:1}48%,100%{opacity:0}}\n@keyframes elAnim-in2{0%,65%{opacity:0}66%,70%{opacity:1}72%,100%{opacity:0}}\n@keyframes elAnim-in3{0%,83%{opacity:0}84%,88%{opacity:1}90%,100%{opacity:0}}\n@keyframes elAnim-hl1{0%,8%{opacity:0}9%,16%{opacity:1}18%,100%{opacity:0}}\n@keyframes elAnim-hl2{0%,24%{opacity:0}25%,33%{opacity:1}35%,100%{opacity:0}}\n@keyframes elAnim-hl3{0%,38%{opacity:0}39%,46%{opacity:1}48%,100%{opacity:0}}\n@keyframes elAnim-out1{0%,42%{opacity:0}43%,97%{opacity:1}100%{opacity:0}}\n@keyframes elAnim-out2{0%,67%{opacity:0}68%,97%{opacity:1}100%{opacity:0}}\n@keyframes elAnim-out3{0%,85%{opacity:0}86%,97%{opacity:1}100%{opacity:0}}\n@keyframes elAnim-loop{0%,53%{color:#9a9a9a}55%,91%{color:#006400}93%,100%{color:#9a9a9a}}\n@keyframes elAnim-mlane{0%,55%{fill:#fafafa;stroke:#d0d0d0}56%,64%{fill:#eef6ee;stroke:#006400}66%,100%{fill:#fafafa;stroke:#d0d0d0}}\n@keyframes elAnim-tlane{0%,73%{fill:#fafafa;stroke:#d0d0d0}74%,82%{fill:#eef6ee;stroke:#006400}84%,100%{fill:#fafafa;stroke:#d0d0d0}}\n@keyframes elAnim-c1{0%,7%{opacity:1}8%,100%{opacity:0}}\n@keyframes elAnim-c2{0%,7%{opacity:0}8%,23%{opacity:1}24%,100%{opacity:0}}\n@keyframes elAnim-c3{0%,23%{opacity:0}24%,37%{opacity:1}38%,100%{opacity:0}}\n@keyframes elAnim-c4{0%,37%{opacity:0}38%,49%{opacity:1}50%,100%{opacity:0}}\n@keyframes elAnim-c5{0%,49%{opacity:0}50%,55%{opacity:1}56%,100%{opacity:0}}\n@keyframes elAnim-c6{0%,55%{opacity:0}56%,73%{opacity:1}74%,100%{opacity:0}}\n@keyframes elAnim-c7{0%,73%{opacity:0}74%,91%{opacity:1}92%,100%{opacity:0}}\n@keyframes elAnim-c8{0%,91%{opacity:0}92%,99%{opacity:1}100%{opacity:0}}\n@media (prefers-reduced-motion:reduce){#el-anim .anim,#el-anim .spin,#el-anim .caret{animation:none}#el-anim .controls{display:none}}\n\u003C\u002Fstyle>\n\u003Crect class=\"panel\" x=\"16\" y=\"16\" width=\"448\" height=\"140\" rx=\"10\"\u002F>\n\u003Crect class=\"hl hl1 anim\" x=\"20\" y=\"26\" width=\"440\" height=\"60\" rx=\"4\"\u002F>\n\u003Crect class=\"hl hl2 anim\" x=\"20\" y=\"86\" width=\"440\" height=\"40\" rx=\"4\"\u002F>\n\u003Crect class=\"hl hl3 anim\" x=\"20\" y=\"126\" width=\"440\" height=\"20\" rx=\"4\"\u002F>\n\u003Crect class=\"hl hl4 anim\" x=\"20\" y=\"46\" width=\"440\" height=\"20\" rx=\"4\"\u002F>\n\u003Crect class=\"hl hl5 anim\" x=\"20\" y=\"106\" width=\"440\" height=\"20\" rx=\"4\"\u002F>\n\u003Ctext class=\"mono code\" x=\"28\" y=\"40\">setTimeout(() =&gt; {\u003C\u002Ftext>\n\u003Ctext class=\"mono code\" x=\"42.4\" y=\"60\">console.log('hey, from set timeout');\u003C\u002Ftext>\n\u003Ctext class=\"mono code\" x=\"28\" y=\"80\">}, 0);\u003C\u002Ftext>\n\u003Ctext class=\"mono code\" x=\"28\" y=\"100\">new Promise((resolve) =&gt; resolve('ahoy from promise'))\u003C\u002Ftext>\n\u003Ctext class=\"mono code\" x=\"42.4\" y=\"120\">.then((v) =&gt; console.log(v));\u003C\u002Ftext>\n\u003Ctext class=\"mono code\" x=\"28\" y=\"140\">console.log('hello from console log');\u003C\u002Ftext>\n\u003Crect class=\"panel\" x=\"16\" y=\"170\" width=\"216\" height=\"190\" rx=\"10\"\u002F>\n\u003Ctext class=\"label\" x=\"28\" y=\"192\">Call stack\u003C\u002Ftext>\n\u003Crect class=\"panel\" x=\"248\" y=\"170\" width=\"216\" height=\"90\" rx=\"10\"\u002F>\n\u003Ctext class=\"label\" x=\"260\" y=\"192\">Web APIs\u003C\u002Ftext>\n\u003Crect class=\"panel\" x=\"248\" y=\"272\" width=\"216\" height=\"88\" rx=\"10\"\u002F>\n\u003Cg class=\"loop anim\">\n\u003Cg class=\"spin\">\n\u003Ccircle cx=\"276\" cy=\"318\" r=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\" stroke-dasharray=\"90 23\"\u002F>\n\u003Cpolygon points=\"286.9,302.4 282.4,296.4 279.8,305.0\" fill=\"currentColor\"\u002F>\n\u003C\u002Fg>\n\u003Ctext x=\"306\" y=\"306\" style=\"font-size:18px;font-weight:600\" fill=\"currentColor\">Event Loop\u003C\u002Ftext>\n\u003C\u002Fg>\n\u003Ctext class=\"hint\" x=\"306\" y=\"325\">stack vide ? → d’abord\u003C\u002Ftext>\n\u003Ctext class=\"hint\" x=\"306\" y=\"340\">les microtasks, puis une task\u003C\u002Ftext>\n\u003Crect class=\"panel mlane anim\" x=\"16\" y=\"376\" width=\"448\" height=\"58\" rx=\"10\"\u002F>\n\u003Ctext class=\"label\" x=\"28\" y=\"392\" style=\"font-size:15px\">Microtask queue\u003C\u002Ftext>\n\u003Ctext class=\"hint\" x=\"454\" y=\"392\" text-anchor=\"end\">Promise, queueMicrotask, MutationObserver\u003C\u002Ftext>\n\u003Crect class=\"panel tlane anim\" x=\"16\" y=\"446\" width=\"448\" height=\"58\" rx=\"10\"\u002F>\n\u003Ctext class=\"label\" x=\"28\" y=\"462\" style=\"font-size:15px\">Task queue\u003C\u002Ftext>\n\u003Ctext class=\"hint\" x=\"454\" y=\"462\" text-anchor=\"end\">setTimeout, événements, opérations réseau\u003C\u002Ftext>\n\u003Crect x=\"16\" y=\"520\" width=\"448\" height=\"144\" rx=\"8\" fill=\"#ffffff\" stroke=\"#cfd1d4\" stroke-width=\"1.5\"\u002F>\n\u003Cpath d=\"M16 546 V528 a8 8 0 0 1 8 -8 H456 a8 8 0 0 1 8 8 V546 Z\" fill=\"#f1f3f4\"\u002F>\n\u003Ctext class=\"ui\" x=\"28\" y=\"537\">Elements\u003C\u002Ftext>\n\u003Ctext class=\"ui\" x=\"90\" y=\"537\" style=\"fill:#202124\">Console\u003C\u002Ftext>\n\u003Ctext class=\"ui\" x=\"146\" y=\"537\">Sources\u003C\u002Ftext>\n\u003Ctext class=\"ui\" x=\"199\" y=\"537\">Network\u003C\u002Ftext>\n\u003Crect x=\"84\" y=\"543\" width=\"52\" height=\"3\" fill=\"#1a73e8\"\u002F>\n\u003Cline class=\"rule\" x1=\"16\" y1=\"546\" x2=\"464\" y2=\"546\"\u002F>\n\u003Ccircle cx=\"31\" cy=\"558\" r=\"5.5\" fill=\"none\" stroke=\"#5f6368\" stroke-width=\"1.2\"\u002F>\n\u003Cline x1=\"27.1\" y1=\"561.9\" x2=\"34.9\" y2=\"554.1\" stroke=\"#5f6368\" stroke-width=\"1.2\"\u002F>\n\u003Ctext class=\"ui\" x=\"46\" y=\"562\">top ▾\u003C\u002Ftext>\n\u003Crect x=\"84\" y=\"551\" width=\"150\" height=\"15\" rx=\"3\" fill=\"#ffffff\" stroke=\"#dadce0\"\u002F>\n\u003Ctext class=\"ui\" x=\"90\" y=\"562\" style=\"fill:#9aa0a6\">Filter\u003C\u002Ftext>\n\u003Ctext class=\"ui\" x=\"454\" y=\"562\" text-anchor=\"end\">Default levels ▾\u003C\u002Ftext>\n\u003Cline class=\"rule\" x1=\"16\" y1=\"570\" x2=\"464\" y2=\"570\"\u002F>\n\u003Cg class=\"out out1 anim\">\u003Ctext class=\"mono\" x=\"28\" y=\"584\">hello from console log\u003C\u002Ftext>\u003Ctext class=\"ui\" x=\"454\" y=\"584\" text-anchor=\"end\" style=\"text-decoration:underline\">script.js:6\u003C\u002Ftext>\u003Cline class=\"rule\" x1=\"16\" y1=\"590\" x2=\"464\" y2=\"590\"\u002F>\u003C\u002Fg>\n\u003Cg class=\"out out2 anim\">\u003Ctext class=\"mono\" x=\"28\" y=\"604\">ahoy from promise\u003C\u002Ftext>\u003Ctext class=\"ui\" x=\"454\" y=\"604\" text-anchor=\"end\" style=\"text-decoration:underline\">script.js:5\u003C\u002Ftext>\u003Cline class=\"rule\" x1=\"16\" y1=\"610\" x2=\"464\" y2=\"610\"\u002F>\u003C\u002Fg>\n\u003Cg class=\"out out3 anim\">\u003Ctext class=\"mono\" x=\"28\" y=\"624\">hey, from set timeout\u003C\u002Ftext>\u003Ctext class=\"ui\" x=\"454\" y=\"624\" text-anchor=\"end\" style=\"text-decoration:underline\">script.js:2\u003C\u002Ftext>\u003Cline class=\"rule\" x1=\"16\" y1=\"630\" x2=\"464\" y2=\"630\"\u002F>\u003C\u002Fg>\n\u003Ctext x=\"26\" y=\"646\" style=\"font-size:14px;font-weight:700\" fill=\"#1a73e8\">›\u003C\u002Ftext>\n\u003Crect class=\"caret\" x=\"38\" y=\"636\" width=\"1.2\" height=\"12\" fill=\"#202124\"\u002F>\n\u003Cg class=\"tok frame g-global anim\" transform=\"translate(26 320)\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">global execution context\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"tok frame g-set anim\" transform=\"translate(26 284)\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">setTimeout(…, 0)\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"tok timer g-timer anim\" transform=\"translate(258 208)\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">minuteur : 0 ms\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"tok frame g-prom anim\" transform=\"translate(26 284)\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">new Promise(…).then(…)\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"tok frame g-log anim\" transform=\"translate(26 284)\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">console.log('hello…')\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"tok frame g-in2 anim\" transform=\"translate(26 284)\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">console.log(v)\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"tok frame g-in3 anim\" transform=\"translate(26 284)\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">console.log('hey…')\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"tok cb g-cb2 anim\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">(v) =&gt; console.log(v)\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"tok cb g-cb1 anim\">\u003Crect width=\"196\" height=\"28\" rx=\"6\"\u002F>\u003Ctext class=\"mono\" x=\"10\" y=\"18.5\">() =&gt; { console.log(…) }\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c1 anim\">\u003Ctext x=\"16\" y=\"690\">1. Le script démarre comme une task. Dans le call stack\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">entre le contexte d’exécution global.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c2 anim\">\u003Ctext x=\"16\" y=\"690\">2. setTimeout() confie le minuteur à la Web API. Après\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">0 ms, son callback est placé dans la Task queue.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c3 anim\">\u003Ctext x=\"16\" y=\"690\">3. La Promise est tenue aussitôt, donc le callback\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">de .then() part dans la Microtask queue.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c4 anim\">\u003Ctext x=\"16\" y=\"690\">4. console.log() s’exécute de façon synchrone sur le call\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">stack et écrit la première ligne dans la console.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c5 anim\">\u003Ctext x=\"16\" y=\"690\">5. Le script est terminé et le call stack est vide.\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">L’Event Loop entre en jeu.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c6 anim\">\u003Ctext x=\"16\" y=\"690\">6. L’Event Loop vide d’abord la Microtask queue :\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">le callback de .then() écrit « ahoy from promise ».\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c7 anim\">\u003Ctext x=\"16\" y=\"690\">7. La Microtask queue est vide ; l’Event Loop prend\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">donc la première task : le callback de setTimeout().\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap cap-end c8 anim\">\u003Ctext x=\"16\" y=\"690\">Résultat : « hello », « ahoy », « hey » – d’abord le code\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">synchrone, puis les microtasks, et ensuite les tasks.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"controls\">\n\u003Cg class=\"btn\" role=\"button\" tabindex=\"0\" aria-label=\"Rejouer l’animation depuis le début\" onclick=\"this.ownerSVGElement.getAnimations({subtree:true}).forEach(function(a){a.currentTime=0;a.play()})\" onkeydown=\"if(event.key==='Enter'||event.key===' '){event.preventDefault();this.onclick()}\">\u003Crect x=\"16\" y=\"731\" width=\"112\" height=\"24\" rx=\"12\"\u002F>\u003Crect class=\"ico\" x=\"27\" y=\"737\" width=\"2\" height=\"12\"\u002F>\u003Cpolygon class=\"ico\" points=\"37,737 37,749 30,743\"\u002F>\u003Ctext x=\"44\" y=\"747\">Rejouer\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"btn\" role=\"button\" tabindex=\"0\" aria-label=\"Mettre l’animation en pause\" onclick=\"this.ownerSVGElement.getAnimations({subtree:true}).forEach(function(a){a.pause()})\" onkeydown=\"if(event.key==='Enter'||event.key===' '){event.preventDefault();this.onclick()}\">\u003Crect x=\"136\" y=\"731\" width=\"96\" height=\"24\" rx=\"12\"\u002F>\u003Crect class=\"ico\" x=\"147\" y=\"737\" width=\"3\" height=\"12\"\u002F>\u003Crect class=\"ico\" x=\"153\" y=\"737\" width=\"3\" height=\"12\"\u002F>\u003Ctext x=\"164\" y=\"747\">Pause\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"btn\" role=\"button\" tabindex=\"0\" aria-label=\"Reprendre l’animation\" onclick=\"this.ownerSVGElement.getAnimations({subtree:true}).forEach(function(a){a.play()})\" onkeydown=\"if(event.key==='Enter'||event.key===' '){event.preventDefault();this.onclick()}\">\u003Crect x=\"240\" y=\"731\" width=\"112\" height=\"24\" rx=\"12\"\u002F>\u003Cpolygon class=\"ico\" points=\"251,737 251,749 260,743\"\u002F>\u003Ctext x=\"266\" y=\"747\">Reprendre\u003C\u002Ftext>\u003C\u002Fg>\n\u003C\u002Fg>\n\u003C\u002Fsvg>\n",1790905681950]