[{"data":1,"prerenderedAt":54},["ShallowReactive",2],{"post-es-cuando-decimos-event-loop":3,"translation-es-cuando-decimos-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>El trabajo de un desarrollador de software exige que aprendamos cosas nuevas y sigamos las tendencias del sector. Ya sea para mejorar nuestra posición en el mercado laboral o simplemente para ampliar nuestros conocimientos y nuestra productividad. En los dos últimos años, a lo que debemos seguir se sumaron primero los indicios iniciales de la inteligencia artificial, en forma de diversos asistentes de IA, copilotos e integraciones de ChatGPT, capaces de sustituir procesos habituales y rutinarios, pero también de generar soluciones a problemas más complejos. Hoy, en el momento de escribir este texto, los principales modelos de lenguaje son capaces de generar, a partir de un solo prompt en Claude Code Fable o ChatGPT Astra, un proyecto entero que se puede desplegar, probar y ejecutar. Del software y la medicina al copywriting y la traducción, los y las profesionales de estos campos se enfrentan ahora a oleadas de despidos. De este llamado \u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FTechnological_unemployment\">desempleo tecnológico\u003C\u002Fa> ya no se habla solo en el ámbito académico. Se ha convertido en tema y en realidad cotidiana de las profesiones de cuello blanco, véase \u003Ca href=\"https:\u002F\u002Fwww.technologyreview.com\u002F2024\u002F01\u002F27\u002F1087041\u002Ftechnological-unemployment-elon-musk-jobs-ai\u002F\">aquí\u003C\u002Fa> o \u003Ca href=\"https:\u002F\u002Fwww.ilo.org\u002Fresource\u002Farticle\u002Fminimizing-negative-effects-ai-induced-technological-unemployment\">aquí\u003C\u002Fa>. No es la primera vez, ni será la última, que los avances tecnológicos vuelven irrelevantes muchos empleos y campos y, a cambio, crean otros nuevos. En esos casos se \u003Ca href=\"https:\u002F\u002Fdictionary.cambridge.org\u002Fdictionary\u002Fenglish\u002Fseparate-the-wheat-from-the-chaff\">separa el grano de la paja\u003C\u002Fa>, y es legítimo preguntarse: ¿quién seguirá siendo útil para las corporaciones en esta economía de mercado AI-powered?\u003C\u002Fp>\n\u003Cp>Los puestos júnior parecen ser el primer blanco, y el más natural. Conocimientos superficiales, una capacidad poco desarrollada para ver los problemas técnicos en el contexto más amplio de los requisitos técnicos, pero también de negocio. La IA está dejando de padecerlo, mientras que los puestos júnior son conocidos precisamente por eso.\u003C\u002Fp>\n\u003Cp>Si miro mi propio campo, el front-end y los runtimes de servidor del back-end node\u002Fdeno\u002Fbun, me parece que un conocimiento más profundo del Event Loop es una de las pocas «hard skills» que, en el mundo de Javascript, separan a un programador júnior de un ingeniero de software sénior. Quien entiende el Event Loop puede responder a varias preguntas importantes, por ejemplo: ¿cómo es posible que Javascript sea un lenguaje de programación single-threaded y, sin embargo, hagamos en él varias cosas a la vez mediante operaciones asíncronas? Sobre el Event Loop se puede escribir mucho. Mis ambiciones son modestas. Voy a mirar el Event Loop de modo que pueda explicar una de las preguntas más habituales en una entrevista de trabajo para un puesto de desarrollador front-end: en qué orden se ejecutan las siguientes operaciones y por qué:\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>Introducción técnica al problema\u003C\u002Fh2>\n\u003Cp>Javascript es \u003Cstrong>single-threaded\u003C\u002Fstrong>. Si dejamos de lado la \u003Ca href=\"https:\u002F\u002Fdeveloper.mozilla.org\u002Fen-US\u002Fdocs\u002FWeb\u002FAPI\u002FWeb_Workers_API\">Web Workers API\u003C\u002Fa>, esto significa que Javascript solo sabe procesar una única operación en cada momento. Javascript está diseñado para procesar las operaciones de forma secuencial con el método LIFO (Last-In, First-Out) y, mientras una operación no ha terminado, bloquea todas las siguientes. En circunstancias normales, por ejemplo, tras hacer clic en un botón, una sola petición GET lenta a una API remota bloquearía toda la interacción del usuario; la interfaz entera del navegador se quedaría congelada hasta que se resolviera la petición de red (network request). Cuando Brendan Eich diseñó Javascript en los años noventa, el procesamiento de las operaciones en Javascript era síncrono, con posibilidades limitadas de tratar los eventos provocados por la interacción del usuario con el navegador. Merecería una indagación histórica averiguar por qué Eich diseñó así Javascript. Hoy, sin embargo, sabemos que las páginas web basadas en Javascript (es decir, toda la web) no se comportan así. El usuario puede interactuar con varias partes de una página web a la vez. ¿Cómo puede lograrlo un lenguaje de programación single-threaded?\u003C\u002Fp>\n\u003Ch2>Enter Javascript runtime\u003C\u002Fh2>\n\u003Cp>Cuando decimos Javascript, tenemos que darnos cuenta de que se trata de una \u003Cstrong>colección de bibliotecas y componentes\u003C\u002Fstrong>. Solo al conectarlos obtenemos un conjunto que funciona: el \u003Cstrong>runtime\u003C\u002Fstrong>, es decir, el entorno en el que se ejecuta nuestra aplicación. Actualmente podemos elegir entre dos tipos principales de runtime. O bien se trata del runtime de un navegador web, o bien del runtime de una de las varias implementaciones de servidor, por ejemplo Node.js, Deno o el más reciente, Bun. Aunque los runtimes de los navegadores y de los servidores tienen elementos en común, también encontramos en ellos diferencias importantes. Estas se derivan de la orientación de cada tipo de runtime. Los navegadores web se orientan al trabajo con el DOM (\u003Ccode>window\u003C\u002Fcode>, \u003Ccode>document\u003C\u002Fcode>), a la interacción del usuario (\u003Ccode>addEventListener\u003C\u002Fcode>), a las operaciones de red mediante \u003Ccode>fetch\u003C\u002Fcode> o \u003Ccode>XMLHttpRequest\u003C\u002Fcode>, al almacenamiento local del navegador (\u003Ccode>localStorage\u003C\u002Fcode>, \u003Ccode>sessionStorage\u003C\u002Fcode>), etc. El runtime de Node.js o de Bun, en cambio, se orienta al entorno de las aplicaciones de servidor, al trabajo con el sistema de archivos y las bases de datos, o a las operaciones de bajo nivel sobre el sistema operativo. \u003Cstrong>Pero ambos tipos de runtime implementan el Event Loop\u003C\u002Fstrong>. Para simplificar, en adelante me ocuparé solo del runtime de los navegadores web.\u003C\u002Fp>\n\u003Ch2>El runtime de los navegadores web\u003C\u002Fh2>\n\u003Cp>El runtime de Javascript de los navegadores web se compone de las siguientes partes:\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 y Microtask queue\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>El Event Loop es solo uno de los componentes, y entra en juego únicamente cuando se procesa código asíncrono. Si en nuestro programa de Javascript usamos solo operaciones síncronas, el Event Loop no se utilizará nunca. Aun así, el Event Loop es una de las características principales de Javascript, y gracias a él las aplicaciones web modernas están llenas de funcionalidades asíncronas. Para entender el Event Loop, y el procesamiento de código asíncrono en Javascript en general, conviene recordar \u003Cstrong>cómo se comporta Javascript con operaciones plenamente síncronas\u003C\u002Fstrong>. Y para eso conviene entender cómo funciona el engine de Javascript.\u003C\u002Fp>\n\u003Ch3>Javascript engine\u003C\u002Fh3>\n\u003Cp>El corazón imaginario de Javascript es el engine. Las funciones principales de un engine de JavaScript son el parsing, la interpretación y la ejecución del código. Por parsing entendemos \u003Cstrong>la conversión del código fuente de JavaScript en el llamado árbol de sintaxis abstracta (AST)\u003C\u002Fstrong>, una estructura que el engine es capaz de procesar. Por interpretación entendemos que \u003Cstrong>la mayoría de los engines modernos primero interpretan el código y después lo compilan a código máquina (just-in-time compilation – JIT)\u003C\u002Fstrong>. Y por ejecución del código entendemos que, \u003Cstrong>tras la traducción a código máquina, el engine empieza a ejecutar las instrucciones y mantiene la aplicación en marcha\u003C\u002Fstrong>.\u003C\u002Fp>\n\u003Cp>Ejemplos de engines de JavaScript populares:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>V8: Lo usan Google Chrome y Node.js. Es conocido por su velocidad y eficiencia.\u003C\u002Fli>\n\u003Cli>SpiderMonkey: El primer engine de JavaScript, desarrollado por Netscape; hoy se usa en Firefox.\u003C\u002Fli>\n\u003Cli>JavaScriptCore (alias Nitro): Lo usan Safari y otros productos de Apple.\u003C\u002Fli>\n\u003Cli>Chakra: Lo usaba Microsoft Edge (legacy); ahora lo sustituye el engine V8 en el nuevo Edge (Chromium).\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>El engine de JavaScript proporciona además dos estructuras de memoria: el \u003Cstrong>heap\u003C\u002Fstrong> y el \u003Cstrong>call stack\u003C\u002Fstrong>. Sus diferencias y su funcionamiento merecerían, de nuevo, un artículo aparte sobre la gestión de memoria en Javascript. La definición básica puede leerse en Wikipedia o sacarse con un prompt a ChatGPT.\u003C\u002Fp>\n\u003Cp>Lo importante para entender el Event Loop es que \u003Cstrong>el Javascript Engine como tal no implementa el procesamiento de código asíncrono\u003C\u002Fstrong>. Toda la asincronía de Javascript hay que implementarla en otra parte. ¿Dónde? De eso se encarga precisamente el Javascript Runtime, que añade al engine de Javascript las Web APIs (estamos en el navegador), el Event loop y las colas de espera, la Task queue y la Microtask queue. Fun fact: si Javascript fuera un lenguaje de programación puramente síncrono, le bastaría con el heap y el call stack y no necesitaría el Event Loop. Pero me estoy adelantando.\u003C\u002Fp>\n\u003Ch3>Heap\u003C\u002Fh3>\n\u003Cp>El \u003Cstrong>heap\u003C\u002Fstrong> no es tan importante para entender el Event Loop. El heap es una estructura de memoria en la que el Javascript Engine guarda los tipos de datos complejos (Object, Array, Function, Map, Set, WeakMap, WeakSet). El tamaño del heap viene dado por el tamaño de la memoria RAM. A diferencia del call stack, el heap no está organizado de forma secuencial; es una memoria no lineal. En el heap se guardan los datos cuyo tipo y tamaño no conocemos durante la compilación y que pueden cambiar durante el runtime (asignación de otros valores a las properties de los objetos, creación de nuevas properties, de nuevos objetos, etc.), de modo que el heap es una memoria dinámica. Técnicamente, el heap es una implementación del patrón de diseño habitual de asignación dinámica de memoria (dynamic memory allocation pattern), lo que significa que requiere un Garbage Collector, que limpia la memoria asignada durante el runtime según si alguna parte del programa necesita todavía (tiene referencias en el call stack a) los objetos asignados en el heap. Si no es así, el Garbage Collector, en circunstancias normales, limpia el heap y libera la memoria. Pero el Garbage Collector no es preciso al 100 %, de modo que, pese a su existencia, pueden seguir produciéndose fugas de memoria (memory leaks).\u003C\u002Fp>\n\u003Ch3>Call Stack\u003C\u002Fh3>\n\u003Cp>El \u003Cstrong>call stack\u003C\u002Fstrong>, por su parte, es una memoria estática más sencilla y mucho más interesante para explicar el Event Loop, porque tiene que ver con el orden en que se ejecuta el código en Javascript. El engine de Javascript trabaja con el call stack solo de forma síncrona, paso a paso. Desde el punto de vista técnico, el call stack es una implementación del clásico \u003Ca href=\"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FStack_(abstract_data_type)\">patrón de diseño de la Pila (Stack)\u003C\u002Fa>, en el que el último elemento añadido a la memoria es el primero en procesarse (Last In, First Out, LIFO). ¿Y qué es exactamente lo que se guarda en el call stack, y el orden de \u003Cstrong>qué\u003C\u002Fstrong> determina exactamente? Para simplificar, podemos imaginar un programa de Javascript como una colección de funciones y variables; el call stack se encarga de que nuestras funciones se llamen y nuestras variables se declaren en el orden correcto y en el momento correcto.\u003C\u002Fp>\n\u003Cp>Mientras que los tipos de datos complejos (objetos, arrays, etc.) se guardan en el heap, en el call stack se guardan solo las referencias\u002Fpunteros al heap. Lo que sí se guarda realmente en el call stack son los valores de los tipos de datos primitivos (string, integer, boolean, etc.), y también todo el \u003Cstrong>contexto de ejecución\u003C\u002Fstrong> de la función, con lo que nos referimos a todo «lo que hace falta para llamar a la función». De nuevo, convendría profundizar en todo lo que significa el contexto de ejecución de una función, pero, para simplificar, el contexto de ejecución incluye:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Variable Environment (entorno de variables)\u003C\u002Fstrong>: El registro de las variables y de las declaraciones de funciones específicas de la función dada.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Lexical Environment (entorno léxico)\u003C\u002Fstrong>: Las referencias al scope superior (entorno léxico) y las variables definidas con let y const.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>El enlace this\u003C\u002Fstrong>: El valor de this en la función actual.\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Detalles específicos de la llamada\u003C\u002Fstrong>: Los argumentos pasados a la función y el mecanismo de su retorno (es decir, el lugar del programa al que este debe volver cuando la función termine).\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cp>Si dejo que Claude genere un ejemplo al azar para mostrar cómo funciona el call stack, tomemos, por ejemplo, el siguiente código:\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>¿Qué ocurre exactamente en un ejemplo tan sencillo? La situación es la siguiente: en el \u003Cstrong>call stack\u003C\u002Fstrong> se guarda primero la función \u003Ccode>calculate()\u003C\u002Fcode>, o mejor dicho, todo su contexto de ejecución. Después se añade al \u003Cstrong>call stack\u003C\u002Fstrong> el contexto de ejecución de la función \u003Ccode>multiply()\u003C\u002Fcode>. En cuanto la función \u003Ccode>multiply()\u003C\u002Fcode> se ha llamado y su resultado se ha asignado a la variable \u003Ccode>product\u003C\u002Fcode>, la función \u003Ccode>multiply()\u003C\u002Fcode> y su contexto de ejecución se retiran del call stack. Luego se guarda en el call stack el contexto de ejecución de \u003Ccode>console.log()\u003C\u002Fcode>. Una vez escrito el valor «200» en la consola del navegador, el contexto de ejecución de \u003Ccode>console.log()\u003C\u002Fcode> se retira del call stack. Por último se retira también el contexto de ejecución de la función \u003Ccode>calculate()\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Fijémonos en que en el ejemplo no hay variables globales y en que la propia llamada a la función calculate() no está, aparentemente, dentro de ninguna función cuyo contexto de ejecución pudiera añadirse al call stack. ¿Cómo llegan entonces al call stack las variables globales y las llamadas a funciones en el scope global?\u003C\u002Fp>\n\u003Ch4>Global Execution Context (GEC)\u003C\u002Fh4>\n\u003Cp>Todo lo que está en el scope global puede entenderse como si se llamara dentro de una única función principal, cuyo contexto de ejecución tiene un nombre y un comportamiento especiales: el \u003Cstrong>global execution context\u003C\u002Fstrong>. Modifiquemos el ejemplo anterior y añadámosle 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 aceptamos que el contexto global de nuestros scripts se trata como una especie de función «main», nuestro script se comportará así: al call stack se añade el contexto de ejecución global, al \u003Cstrong>entorno de variables\u003C\u002Fstrong> se añade la variable «globalA», \u003Cstrong>al entorno léxico se añade\u003C\u002Fstrong> la variable «globalB», y se crea el \u003Cstrong>enlace this\u003C\u002Fstrong>, que en el entorno del navegador apuntará al objeto global \u003Cstrong>window\u003C\u002Fstrong>. En cuanto al objeto \u003Cstrong>window\u003C\u002Fstrong>, conviene recordar que, mientras que \u003Cstrong>var globalA\u003C\u002Fstrong> se añade como property al objeto \u003Cstrong>window\u003C\u002Fstrong>, las variables definidas con \u003Cstrong>const\u003C\u002Fstrong> o \u003Cstrong>let\u003C\u002Fstrong> se añaden al scope global\u002Fentorno léxico global, pero no al objeto global \u003Cstrong>window\u003C\u002Fstrong>.\u003C\u002Fp>\n\u003Cp>En cuanto todas las funciones definidas «dentro» del contexto de ejecución global se han añadido al call stack, se han procesado y se han retirado de él, en el call stack queda solo el contexto de ejecución global como última función; al final también este se retira, y el call stack queda vacío.\u003C\u002Fp>\n\u003Cp>Así funciona, al menos en principio, siempre que todo el código que procesa el engine de Javascript sea \u003Cstrong>síncrono\u003C\u002Fstrong>. Si en nuestra aplicación (léase: dentro del contexto de ejecución global) tenemos funciones que se procesan de forma asíncrona, la situación es bastante distinta.\u003C\u002Fp>\n\u003Cp>Hemos dicho que el \u003Cstrong>global execution context\u003C\u002Fstrong> (GEC) es una especie de función «main» inicial y el punto de entrada de la aplicación. Parece que tiene que estar en ejecución todo el tiempo, hasta que se haya procesado todo el código. Pero ¿qué pasa con las partes asíncronas del código? Estas pueden, al fin y al cabo, poner en marcha otras funciones que devuelven nuevos valores y crean nuevos contextos de ejecución, de modo que el contexto de ejecución global tiene que estar presente en el call stack «de algún modo». ¿Y cómo y cuándo se llama al código asíncrono cuando el call stack está parcialmente lleno, o cuando está completamente vacío? Y si el call stack está vacío, ¿cómo es posible que en Javascript se ejecute algo, por ejemplo cuando el usuario hace clic en un botón y se ejecuta el callback de un event listener registrado con addEventListener()? ¿Y qué vigila los callbacks asíncronos de addEventListener, si el engine de Javascript es totalmente síncrono y ni siquiera contiene ninguna función addEventListener?\u003C\u002Fp>\n\u003Ch2>La asincronía en Javascript, o Web APIs, Task queues, Event loop\u003C\u002Fh2>\n\u003Cp>El engine del lenguaje de programación Javascript no contiene ni el objeto \u003Ccode>console\u003C\u002Fcode> ni su método \u003Ccode>console.log()\u003C\u002Fcode>. Lo cual puede sorprender. El hecho es que funcionalidades tan habituales para los programadores como \u003Ccode>setTimeout\u003C\u002Fcode>, \u003Ccode>addEventListener\u003C\u002Fcode>, las operaciones para manipular el \u003Ccode>DOM\u003C\u002Fcode> o las peticiones de red asíncronas \u003Ccode>fetch\u003C\u002Fcode> o \u003Ccode>XMLHttpRequest\u003C\u002Fcode> no forman parte del engine de Javascript, sino de otro componente del Javascript runtime: la Web API.\u003C\u002Fp>\n\u003Ch3>Web API\u003C\u002Fh3>\n\u003Cp>¿Cómo consigue exactamente el engine de Javascript colaborar con la Web API de modo que el Engine tenga acceso a las funciones \u003Ccode>console.log()\u003C\u002Fcode> o \u003Ccode>setTimeout()\u003C\u002Fcode>, si no forman parte directamente del engine? El Javascript Engine (por ejemplo, V8) tiene un \u003Cstrong>registro interno de funciones nativas\u003C\u002Fstrong>, definidas en la especificación ECMAScript (p. ej., Math.random(), Array.prototype.map(), Date.now()). Gracias a él, el Engine reconoce que \u003Ccode>setTimeout()\u003C\u002Fcode> no es una función nativa, y por eso acude al scope global, o mejor dicho al objeto \u003Ccode>window\u003C\u002Fcode>, e intenta llamar al método \u003Ccode>window.setTimeout()\u003C\u002Fcode>. Y es que el Javascript runtime se asegura, antes incluso de inicializar el programa, de que el contexto de ejecución global (y, por tanto, el entorno léxico de cada función, véase más arriba) contenga los objetos que forman parte de la Web API, p. ej., document, navigator, performance, Intl, crypto, console.\u003C\u002Fp>\n\u003Cp>Cuando le pedí a ChatGPT que me explicara el proceso de ejecución del método \u003Ccode>console.log()\u003C\u002Fcode>, me escribió, erróneamente, que \u003Ccode>console.log()\u003C\u002Fcode> forma parte del propio engine de Javascript. Cuando le señalé que no es así, ChatGPT se disculpó y me ofreció una respuesta más precisa. Me interesaba saber por qué había cometido el error. Lo achacó a que, supuestamente, entre los desarrolladores (y, por tanto, también entre los blogueros) está extendido un modelo mental del funcionamiento de Javascript en el que \u003Ccode>console.log()\u003C\u002Fcode> es una operación síncrona y, por tanto, se procesa directamente en el call stack (lo cual es cierto), y de ahí se sigue que \u003Cstrong>si algo se procesa directamente en el call stack (y no necesita callbacks, que se procesan de forma asíncrona fuera del hilo principal de Javascript, ni por tanto el Event loop), tiene que formar parte del engine de Javascript.\u003C\u002Fstrong>\u003C\u002Fp>\n\u003Cp>Eso es un error. El método \u003Ccode>console.log()\u003C\u002Fcode> no existe en el engine de Javascript; este no sabe nada de él, salvo que no es una función nativa. Entrega su procesamiento concreto al Javascript runtime, donde el navegador (o nodejs) ejecuta la función mediante código de bajo nivel en C++ a través de los bindings de C++, que son las relaciones entre una función de Javascript y la función de C++ que realmente hace lo que le pedimos a la función de Javascript.\u003C\u002Fp>\n\u003Cp>Ya sabemos, a grandes rasgos, cómo funciona cuando el engine de Javascript necesita llamar a una función no nativa de la Web API. Pero ¿qué pasa con la asincronía y con las llamadas a \u003Ccode>setTimeout()\u003C\u002Fcode>, \u003Ccode>addEventListener()\u003C\u002Fcode> o \u003Ccode>fetch()\u003C\u002Fcode>?\u003C\u002Fp>\n\u003Ch3>Callbacks y Task Queues en general\u003C\u002Fh3>\n\u003Cp>Como lenguaje single-threaded, el engine de Javascript necesita delegar fuera de sí mismo la ejecución de las operaciones asíncronas. Además de la Web API, el Javascript runtime contiene otros componentes que hacen posibles las operaciones asíncronas: las Task queues y el Event loop. Todas las operaciones asíncronas de Javascript se resuelven mediante callbacks (también \u003Ccode>await\u002Fasync\u003C\u002Fcode>, que es sugar code que usa la asincronía basada en Promises). Cuando el Engine se encuentra con una operación asíncrona, entrega su procesamiento por completo a la Web API. Si se trata de un temporizador como \u003Ccode>setTimeout()\u003C\u002Fcode>, la Web API espera el número correspondiente de milisegundos; si se trata de una network request como \u003Ccode>fetch\u003C\u002Fcode>, de nuevo le corresponde a la Web API esperar la respuesta de esa network request. En cuanto la Web API termina de procesar la operación asíncrona en sí, toma el \u003Cstrong>callback\u003C\u002Fstrong> definido por el programador y pasa esa función callback a un componente especial del Javascript runtime, una cola llamada \u003Cstrong>Task queue\u003C\u002Fstrong> o \u003Cstrong>Microtask\u003C\u002Fstrong> \u003Cstrong>queue\u003C\u002Fstrong>.\u003C\u002Fp>\n\u003Cp>Los callbacks permanecen en las colas hasta que el call stack está completamente vacío, o mejor dicho, hasta que en él queda solo el contexto de ejecución global (GEC). En cuanto en el call stack está solo el GEC, le llega el turno a otro componente: el Event Loop.\u003C\u002Fp>\n\u003Ch3>Task queue\u003C\u002Fh3>\n\u003Cp>La Task queue es la más antigua de las dos colas y, en cierto sentido, es tan antigua como el propio Javascript. Ya las primeras versiones de Javascript en Netscape Navigator conocían \u003Ccode>setTimeout()\u003C\u002Fcode> y sabían reaccionar a los eventos provocados por el usuario, y ambas cosas necesitaban un lugar donde el callback esperase hasta que el engine terminara lo que estaba haciendo. Formalmente, sin embargo, la Task queue (y el Event loop en general) no se describió hasta la especificación HTML5 de la organización WHATWG, que unificó cómo deben comportarse en este aspecto todos los navegadores.\u003C\u002Fp>\n\u003Cp>La razón de ser de la Task queue es aquella con la que empezamos: un engine single-threaded no puede esperar a nada. Por eso la Web API resuelve la espera fuera del engine y luego entrega a la Task queue solo el callback ya listo, que se procesa cuando le llega el turno. A la Task queue (la especificación HTML las llama \u003Cstrong>tasks\u003C\u002Fstrong>; en los blogs encontramos a menudo también la denominación \u003Cstrong>macrotasks\u003C\u002Fstrong>) pertenecen:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>los callbacks de los temporizadores \u003Ccode>setTimeout()\u003C\u002Fcode> y \u003Ccode>setInterval()\u003C\u002Fcode>,\u003C\u002Fli>\n\u003Cli>los callbacks de los eventos registrados con \u003Ccode>addEventListener()\u003C\u002Fcode> (un clic, la pulsación de una tecla, el scroll…),\u003C\u002Fli>\n\u003Cli>los eventos de red de las API más antiguas, por ejemplo \u003Ccode>onload\u003C\u002Fcode> en \u003Ccode>XMLHttpRequest\u003C\u002Fcode> o los mensajes de un \u003Ccode>WebSocket\u003C\u002Fcode>,\u003C\u002Fli>\n\u003Cli>los mensajes entre ventanas y workers enviados con \u003Ccode>postMessage()\u003C\u002Fcode>,\u003C\u002Fli>\n\u003Cli>y, en general, la propia ejecución de un script desde la etiqueta \u003Ccode>&lt;script&gt;\u003C\u002Fcode>: también nuestro contexto de ejecución global empieza su vida como una task.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Hay dos cosas que merece la pena mencionar. Primero, «Task queue» en singular es una simplificación. La especificación permite al navegador tener varias colas (por ejemplo, una para las interacciones del usuario y otra para los temporizadores) y elegir entre ellas, de modo que el navegador puede dar prioridad a un clic frente a un temporizador. Dentro de una misma cola, sin embargo, rige el método FIFO. Segundo, \u003Cstrong>el Event loop toma de la Task queue siempre una sola task\u003C\u002Fstrong>, y entre dos tasks el navegador tiene ocasión de repintar la página. Por eso ni siquiera \u003Ccode>setTimeout(callback, 0)\u003C\u002Fcode> significa «ahora mismo», sino «como pronto en la siguiente vuelta del Event loop». Además, con llamadas a temporizadores muy anidadas, los navegadores elevan el retardo mínimo a 4 ms.\u003C\u002Fp>\n\u003Ch3>Microtask queue\u003C\u002Fh3>\n\u003Cp>La Microtask queue es bastante más joven. El término \u003Cstrong>microtask\u003C\u002Fstrong> apareció hacia 2011, cuando ingenieros de Mozilla y de Chromium proponían un sustituto de los llamados Mutation Events, un mecanismo antiguo con el que se podían seguir los cambios en el DOM. Los Mutation Events se disparaban de forma síncrona con cada cambio individual del DOM, lo cual era lento y propenso a errores. La nueva API \u003Ccode>MutationObserver\u003C\u002Fcode> debía entregar los cambios de forma asíncrona y todos a la vez, en un lote, pero al mismo tiempo antes de que el navegador repintara la página o procesara el siguiente evento. La Task queue no bastaba para eso, porque entre dos tasks ya puede producirse un repintado o el procesamiento de otro clic. Por eso nació una nueva cola, cuyos callbacks se procesan \u003Cstrong>en cuanto el call stack se vacía, antes aún de la siguiente task\u003C\u002Fstrong>.\u003C\u002Fp>\n\u003Cp>El segundo uso de la Microtask queue, y hoy el más conocido, fueron las \u003Cstrong>Promises\u003C\u002Fstrong>, estandarizadas en ECMAScript 2015 (ES6). La especificación de ECMAScript las llama «jobs», pero el navegador las coloca precisamente en la Microtask queue. Con ello las Promises recibieron una garantía importante: el callback de \u003Ccode>.then()\u003C\u002Fcode> nunca se llama de forma síncrona, pero tampoco innecesariamente tarde; siempre justo después de que termine el código que se está ejecutando. Hacia 2019 los navegadores añadieron además la función \u003Ccode>queueMicrotask()\u003C\u002Fcode>, con la que también el programador puede programar una microtask directamente, sin dar el rodeo por una Promise.\u003C\u002Fp>\n\u003Cp>A la Microtask queue pertenecen:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>los callbacks de las Promises: \u003Ccode>.then()\u003C\u002Fcode>, \u003Ccode>.catch()\u003C\u002Fcode>, \u003Ccode>.finally()\u003C\u002Fcode>,\u003C\u002Fli>\n\u003Cli>la continuación de una función async después de \u003Ccode>await\u003C\u002Fcode> (async\u002Fawait está construido sobre Promises),\u003C\u002Fli>\n\u003Cli>los callbacks pasados a \u003Ccode>queueMicrotask()\u003C\u002Fcode>,\u003C\u002Fli>\n\u003Cli>los callbacks de \u003Ccode>MutationObserver\u003C\u002Fcode>.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>Y una regla importante más: mientras que de la Task queue el Event loop toma en cada momento exactamente una task, \u003Cstrong>la Microtask queue la vacía siempre entera\u003C\u002Fstrong>, incluidas las microtasks que se han añadido durante su procesamiento. Si alguna microtask añadiera otra microtask, el navegador ya no llegaría ni al clic ni a repintar la página, y la página se quedaría congelada, igual que con un bucle infinito en código síncrono.\u003C\u002Fp>\n\u003Ch2>Event Loop\u003C\u002Fh2>\n\u003Cp>El misterioso Event loop resulta ser ahora algo relativamente trivial. El Event loop no es más que un componente del Javascript runtime que comprueba constantemente si el call stack está lleno. Si está vacío, la siguiente tarea del Event loop es decidir si toma, con el método FIFO, el primer callback de la Task queue o de la Microtask queue. Eso es todo.\u003C\u002Fp>\n\u003Csvg id=\"el-anim\" xmlns=\"http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg\" viewBox=\"0 0 480 764\" role=\"img\" aria-label=\"Animación: cómo procesa el Event Loop el ejemplo inicial\" 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\">El script se ejecuta en el contexto de ejecución global. setTimeout entrega el temporizador a la Web API y, tras 0 ms, su callback pasa a la Task queue. El callback de .then() de la Promise ya cumplida pasa a la Microtask queue. console.log se ejecuta de forma síncrona y escribe en la consola «hello from console log». Cuando el call stack se vacía, el Event Loop procesa primero la Microtask queue y escribe «ahoy from promise»; solo después toma la task de la Task queue y escribe «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 vacío? → primero\u003C\u002Ftext>\n\u003Ctext class=\"hint\" x=\"306\" y=\"340\">todas las microtasks, luego una 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, eventos, operaciones de red\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\">temporizador: 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. El script arranca como una task. En el call stack\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">entra el contexto de ejecución global.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c2 anim\">\u003Ctext x=\"16\" y=\"690\">2. setTimeout() entrega el temporizador a la Web API.\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">Tras 0 ms, su callback pasa a la Task queue.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c3 anim\">\u003Ctext x=\"16\" y=\"690\">3. La Promise se cumple al instante, así que el callback\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">de .then() va a la Microtask queue.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c4 anim\">\u003Ctext x=\"16\" y=\"690\">4. console.log() se ejecuta de forma síncrona en el call\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">stack y escribe la primera línea en la consola.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c5 anim\">\u003Ctext x=\"16\" y=\"690\">5. El script ha terminado y el call stack está vacío.\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">Entra en juego el Event Loop.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c6 anim\">\u003Ctext x=\"16\" y=\"690\">6. El Event Loop vacía primero la Microtask queue:\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">el callback de .then() escribe «ahoy from promise».\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap c7 anim\">\u003Ctext x=\"16\" y=\"690\">7. La Microtask queue está vacía, así que el Event Loop\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">toma la primera task: el callback de setTimeout().\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"cap cap-end c8 anim\">\u003Ctext x=\"16\" y=\"690\">Resultado: «hello», «ahoy», «hey»: primero el código\u003C\u002Ftext>\u003Ctext x=\"16\" y=\"710\">síncrono, luego las microtasks y solo después las tasks.\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"controls\">\n\u003Cg class=\"btn\" role=\"button\" tabindex=\"0\" aria-label=\"Reproducir la animación desde el principio\" 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\">Reiniciar\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"btn\" role=\"button\" tabindex=\"0\" aria-label=\"Pausar la animación\" 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\">Pausar\u003C\u002Ftext>\u003C\u002Fg>\n\u003Cg class=\"btn\" role=\"button\" tabindex=\"0\" aria-label=\"Continuar la animación\" 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\">Continuar\u003C\u002Ftext>\u003C\u002Fg>\n\u003C\u002Fg>\n\u003C\u002Fsvg>\n",1790905688681]