PageSourceSearch

https://simoneamico.com/assets/js/816bad59.797f404d.js

js simoneamico.com collected 2026-10-09 00:14:02 UTC 157,584 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunkmotore_temp=globalThis.webpackChunkmotore_temp||[]).push([[1471],{4605:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>l,contentTitle:()=>c,default:()=>u,frontMatter:()=>o,metadata:()=>s,toc:()=>d});const s=JSON.parse('{"id":"vademecum/javascript-real-world/browser-and-async","title":"Browser, Async, and Patterns","description":"DOM, events, dialog, fetch, promises, async/await, storage, service workers, advanced patterns, and testing.","source":"@site/docs/03-vademecum/javascript-real-world/04-browser-and-async.mdx","sourceDirName":"03-vademecum/javascript-real-world","slug":"/vademecum/javascript-real-world/browser-and-async","permalink":"/docs/vademecum/javascript-real-world/browser-and-async","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":4,"frontMatter":{"sidebar_position":4,"title":"Browser, Async, and Patterns","description":"DOM, events, dialog, fetch, promises, async/await, storage, service workers, advanced patterns, and testing."},"sidebar":"tutorialSidebar","previous":{"title":"OOP and Modules","permalink":"/docs/vademecum/javascript-real-world/oop-and-modules"},"next":{"title":"Philosophy and JSX","permalink":"/docs/vademecum/react-real-world/philosophy-jsx"}}');var r=t(4848),a=t(8453),i=t(8774);const o={sidebar_position:4,title:"Browser, Async, and Patterns",description:"DOM, events, dialog, fetch, promises, async/await, storage, service workers, advanced patterns, and testing."},c="JavaScript Real World Vademecum",l={},d=[{value:"DOM and Interactivity",id:"dom-and-interactivity",level:2},{value:"19. Script Loading (When Your JS Runs)",id:"19-script-loading-when-your-js-runs",level:3},{value:"The Problem: <code>&lt;script&gt;</code> Blocks the Parser",id:"the-problem-script-blocks-the-parser",level:4},{value:"The Classic Solution: <code>&lt;script&gt;</code> at the End of <code>&lt;body&gt;</code>",id:"the-classic-solution-script-at-the-end-of-body",level:4},{value:"The Modern Solution: <code>defer</code>
1",id:"the-modern-solution-defer",level:4},{value:"<code>async</code> (For Independent Scripts)",id:"async-for-independent-scripts",level:4},{value:"<code>type=&quot;module&quot;</code> Is <code>defer</code> by Default",id:"typemodule-is-defer-by-default",level:4},{value:"<code>DOMContentLoaded</code> (When the DOM Is Ready)",id:"domcontentloaded-when-the-dom-is-ready",level:4},{value:"20. DOM Manipulation (Reading and Modifying the Page)",id:"20-dom-manipulation-reading-and-modifying-the-page",level:3},{value:"Selecting Elements",id:"selecting-elements",level:4},{value:"<code>HTMLCollection</code> vs <code>NodeList</code> (Live vs Static)",id:"htmlcollection-vs-nodelist-live-vs-static",level:4},{value:"Properties vs Methods",id:"properties-vs-methods",level:4},{value:"Creating and Modifying Elements",id:"creating-and-modifying-elements",level:4},{value:"<code>textContent</code> vs <code>innerHTML</code> (Safe vs Dangerous)",id:"textcontent-vs-innerhtml-safe-vs-dangerous",level:4},{value:"Styles: <code>style</code> vs <code>classList</code>",id:"styles-style-vs-classlist",level:4},{value:"<code>data-*</code> as a Bridge Between HTML and JS",id:"data--as-a-bridge-between-html-and-js",level:4},{value:"<code>navigator.clipboard</code> (Copying to the Clipboard)",id:"navigatorclipboard-copying-to-the-clipboard",level:4},{value:"21. Events (Reacting to Interactions)",id:"21-events-reacting-to-interactions",level:3},{value:"<code>addEventListener</code> (Always, Never <code>onclick</code>)",id:"addeventlistener-always-never-onclick",level:4},{value:"The <code>event</code> Object",id:"the-event-object",level:4},{value:"Event Delegation (One Listener for Many Elements)",id:"event-delegation-one-listener-for-many-elements",level:4},{value:"Common Events",id:"common-events",level:4},{value:"The <code>this</code> Problem with <code>addEventListener</code> in Classes",id:"the-this-problem-with-addeventlistener-in-classes",level:4},{value:"22. Dialogs and Modals (The <code>&lt;dialog&gt;</code> Tag)",id:"22-dialogs-and-modals-the-dialog-tag",level:3},{value:"Basic Structure",id:"basic-structure",level:4},{value:"<code>showModal()</code> vs <code>show()</code>",id:"showmodal-vs-show",level:4},{value:"Closing the Dialog and <code>returnValue</code>",id:"closing-the-dialog-and-returnvalue",level:4},{value:"Built-In Accessibility",id:"built-in-accessibility",level:4},{value:"Asynchrony",id:"asynchrony",level:2},{value:"23. Synchronous vs Asynchronous",id:"23-synchronous-vs-asynchronous",level:3},{value:"Synchronous Example",id:"synchronous-example",level:4},{value:"The Problem: Slow Operations",id:"the-problem-slow-operations",level:4},{value:"The Three Forms of Async",id:"the-three-forms-of-async",level:4},{value:"The Event Loop (How It Really Works)",id:"the-event-loop-how-it-really-works",level:4},{value:"24. Promises and <code>fetch</code>",id:"24-promises-and-fetch",level:3},{value:"The Three States",id:"the-three-states",level:4},{value:"<code>.then()</code>, <code>.catch()</code>, <code>.finally()</code>",id:"then-catch-finally",level:4},{value:"Promise Chain",id:"promise-chain",level:4},{value:"<code>fetch()</code> (HTTP Calls)",id:"fetch-http-calls",level:4},{value:"The Trap: <code>fetch</code> Does Not Reject on HTTP Errors",id:"the-trap-fetch-does-not-reject-on-http-errors",level:4},{value:"Methods Other than GET",id:"methods-other-than-get",level:4},{value:"Building URLs with <code>URL</code> and <code>URLSearchParams</code>",id:"building-urls-with-url-and-urlsearchparams",level:4},{value:"<code>AbortController</code> (Canceling a <code>fetch</code>)",id:"abortcontroller-canceling-a-fetch",level:4},{value:"25. <code>async</code>/<code>await</code> (Async That Looks Synchronous)",id:"25-asyncawait-async-that-looks-synchronous",level:3},{value:"Basic Syntax",id:"basic-syntax",level:4},{value:"Handling Errors with <code>try</code>/<code>catch</code>",id:"handling-errors-with-trycatch",level:4},{value:"Two Error Levels",id:"two-error-levels",level:4},{value:"<code>await</code> Without <code>async</code> Is an Error",id:"await-without-async-is-an-error",level:4},{value:"Parallel Operations with <code>Promise.all</code>
1",id:"parallel-operations-with-promiseall",level:4},{value:"Custom Error Classes",id:"custom-error-classes",level:4},{value:"Storage, PWA, and Patterns",id:"storage-pwa-and-patterns",level:2},{value:"26. Storage (<code>localStorage</code> and <code>IndexedDB</code>)",id:"26-storage-localstorage-and-indexeddb",level:3},{value:"<code>localStorage</code>",id:"localstorage",level:4},{value:"When to Use <code>localStorage</code>",id:"when-to-use-localstorage",level:4},{value:"<code>IndexedDB</code> (Storage for Large Data)",id:"indexeddb-storage-for-large-data",level:4},{value:"Cookies (Brief Mention)",id:"cookies-brief-mention",level:4},{value:"27. Service Workers and PWA (Notes)",id:"27-service-workers-and-pwa-notes",level:3},{value:"What a Service Worker Can Do",id:"what-a-service-worker-can-do",level:4},{value:"Registering a Service Worker",id:"registering-a-service-worker",level:4},{value:"Cache Strategies",id:"cache-strategies",level:4},{value:"Detecting Updates",id:"detecting-updates",level:4},{value:"When It Makes Sense to Add a Service Worker",id:"when-it-makes-sense-to-add-a-service-worker",level:4},{value:"Web Workers (Notes)",id:"web-workers-notes",level:4},{value:"28. Regular Expressions (Regex)",id:"28-regular-expressions-regex",level:3},{value:"Declaring a Regex",id:"declaring-a-regex",level:4},{value:"The Methods",id:"the-methods",level:4},{value:"Basic Syntax",id:"basic-syntax-1",level:4},{value:"Common Use Patterns",id:"common-use-patterns",level:4},{value:"When Not to Use Regex",id:"when-not-to-use-regex",level:4},{value:"29. Essential Advanced Patterns",id:"29-essential-advanced-patterns",level:3},{value:"Debounce and Throttle",id:"debounce-and-throttle",level:4},{value:"Escape HTML and Sanitization (<code>escapeHtml</code>)",id:"escape-html-and-sanitization-escapehtml",level:4},{value:"Configuration-Driven Architecture",id:"configuration-driven-architecture",level:4},{value:"Immutability",id:"immutability",level:4},{value:"30. Testing (Notes)",id:"30-testing-notes",level:3},{value:"Two Types of Tests",id:"two-types-of-tests",level:4},{value:"The Right Tool for the Right Level",id:"the-right-tool-for-the-right-level",level:4},{value:"When Tests Are Not Needed",id:"when-tests-are-not-needed",level:4},{value:"Typical Setup",id:"typical-setup",level:4},{value:"Summary (Browser and Async in Brief)",id:"summary-browser-and-async-in-brief",level:3}];function h(e){const n={code:"code",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",hr:"hr",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",ul:"ul",...(0,a.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)("p",{className:"article-meta",children:(0,r.jsx)("time",{dateTime:"2026-06-05",children:"Updated on June 5, 2026"})}),"\n",(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"javascript-real-world-vademecum",children:"JavaScript Real World Vademecum"})}),"\n",(0,r.jsx)(n.h1,{id:"part-iv-browser-async-and-patterns",children:"Part IV: Browser, Async, and Patterns"}),"\n",(0,r.jsx)(n.p,{children:"So far we have worked on the language: types, structures, classes, modules.\nFrom here on, JavaScript enters the browser, where it reads and modifies HTML, reacts to clicks and input, communicates with remote servers, and saves data when it must stay available after a refresh.\nThis part will show you how to connect code to the page, the network, and browser state."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"dom-and-interactivity",children:"DOM and Interactivity"}),"\n",(0,r.jsx)(n.h3,{id:"19-script-loading-when-your-js-runs",children:"19. Script Loading (When Your JS Runs)"}),"\n",(0,r.jsxs)(n.p,{children:["Before writing even one line of code that touches the DOM, you need to understand ",(0,r.jsx)(n.strong,{children:"when"})," the browser runs your JavaScript.\nThe moment when the ",(0,r.jsx)(n.code,{children:"<script>"})," tag is executed drastically changes what you can and cannot do."]}),"\n",(0,r.jsxs)(n.h4,{id:"the-problem-script-blocks-the-parser",children:["The Problem: ",(0,r.jsx)(n.code,{children:"<script>"})," Blocks the Parser"]}),"\n",(0,r.jsxs)(n.p,{children:["When the browser reads HTML from top to bottom and meets a ",(0,r.jsx)(n.code,{children:"<script>"}),", it normally stops: it downloads the file, runs it, and only then continues interpreting the rest of the HTML.\nThis behavior is called ",(0,r.jsx)(n.strong,{children:"parser blocking"}),".\nThe parser is the part of the browser that reads HTML and builds the DOM, meaning the tree of elements on the page; if it is blocked, the page cannot continue being built (and therefore displayed)."]}),"\n",(0,r.jsxs)(n.p,{children:["The reason has historical roots, but it is still true for classic scripts without ",(0,r.jsx)(n.code,{children:"defer"}),", ",(0,r.jsx)(n.code,{children:"async"}),", or ",(0,r.jsx)(n.code,{children:'type="module"'}),": JavaScript can modify the HTML while the browser is still reading it and, with ",(0,r.jsx)(n.code,{children:"document.write()"})," (an old method that writes new HTML directly into the document), a script can insert new tags into the page, so the browser must stop the parser, run the script, and only then continue, because the page content may have changed."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-html",children:'<head>\n  <script src="app.js"><\/script>\n  \x3c!-- Here the parser stops until app.js is downloaded and executed --\x3e\n</head>\n<body>\n  <h1>My site</h1>\n</body>\n'})}),"\n",(0,r.jsxs)(n.p,{children:["This creates two problems.\nThe first is ",(0,r.jsx)(n.strong,{children:"performance"}),", meaning perceived speed: the user sees the page later because the browser waits for JavaScript.\nThe second is that if your script tries to access the page DOM, ",(0,r.jsx)(n.strong,{children:"the elements do not exist yet"}),": the parser has not reached the ",(0,r.jsx)(n.code,{children:"<body>"}),"."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// app.js, loaded in the head\nconst h1 = document.querySelector("h1");\nconsole.log(h1); // null! The h1 has not been read by the parser yet\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"the-classic-solution-script-at-the-end-of-body",children:["The Classic Solution: ",(0,r.jsx)(n.code,{children:"<script>"})," at the End of ",(0,r.jsx)(n.code,{children:"<body>"})]}),"\n",(0,r.jsxs)(n.p,{children:["For years the standard practice was to put ",(0,r.jsx)(n.code,{children:"<script>"})," tags ",(0,r.jsxs)(n.strong,{children:["at the end of ",(0,r.jsx)(n.code,{children:"<body>"})]}),", right before the closing tag.\nThis way the parser has already built the whole DOM before meeting them."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-html",children:'<body>\n  <h1>My site</h1>\n  \x3c!-- All content --\x3e\n  <script src="app.js"><\/script>\n</body>\n'})}),"\n",(0,r.jsx)(n.p,{children:"It works well, but it is not optimal because the JavaScript file download starts very late, at the end of the page."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"the-modern-solution-defer",children:["The Modern Solution: ",(0,r.jsx)(n.code,{children:"defer"})]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"defer"})," attribute solves both problems in one line.\nYou put it in the head, and the browser:"]}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsx)(n.li,{children:"Starts downloading the file in the background, meaning behind the scenes, while it keeps reading the HTML"}),"\n",(0,r.jsxs)(n.li,{children:["Runs the script ",(0,r.jsx)(n.strong,{children:"only after"})," it has finished building the whole DOM"]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-html",children:'<head>\n  <script src="app.js" defer><\/script>\n</head>\n<body>\n  <h1>My site</h1>\n</body>\n'})}),"\n",(0,r.jsxs)(n.p,{children:["It depends on your context, but in most cases you will use ",(0,r.jsx)(n.code,{children:"defer"}),", because your app's JavaScript needs the DOM to have already been built."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"async-for-independent-scripts",children:[(0,r.jsx)(n.code,{children:"async"})," (For Independent Scripts)"]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"async"})," attribute is similar to ",(0,r.jsx)(n.code,{children:"defer"})," but more aggressive: it downloads in the background and ",(0,r.jsx)(n.strong,{children:"runs as soon as the file is ready"}),", without waiting for the complete DOM, meaning the parser stops during execution, but not during download."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-html",children:'<script src="analytics.js" async><\/script>\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The typical use is for scripts that are independent from the DOM, such as analytics, tracking, and ads.\nSo it depends on your context, but you will probably tend to use ",(0,r.jsx)(n.code,{children:"defer"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["The key difference: with ",(0,r.jsx)(n.code,{children:"defer"}),", script order is preserved and all scripts run when the DOM is ready.\nWith ",(0,r.jsx)(n.code,{children:"async"}),", order is not guaranteed and scripts can start earlier."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"typemodule-is-defer-by-default",children:[(0,r.jsx)(n.code,{children:'type="module"'})," Is ",(0,r.jsx)(n.code,{children:"defer"})," by Default"]}),"\n",(0,r.jsxs)(n.p,{children:["As we saw in ",(0,r.jsx)(i.A,{to:"/docs/vademecum/javascript-real-world/oop-and-modules#script-typemodule-using-modules-in-the-browser",children:"section 17"}),", ",(0,r.jsx)(n.code,{children:'<script type="module">'})," automatically has the behavior of ",(0,r.jsx)(n.code,{children:"defer"}),".\nYou do not need to add anything."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-html",children:'<script type="module" src="app.js"><\/script>\n\x3c!-- Already behaves like defer --\x3e\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"domcontentloaded-when-the-dom-is-ready",children:[(0,r.jsx)(n.code,{children:"DOMContentLoaded"})," (When the DOM Is Ready)"]}),"\n",(0,r.jsxs)(n.p,{children:["If for some reason you cannot use ",(0,r.jsx)(n.code,{children:"defer"})," (for example you are working on legacy code, meaning old existing code, with inline scripts), there is another way to know when the DOM is ready: the ",(0,r.jsx)(n.code,{children:"DOMContentLoaded"})," event."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'document.addEventListener("DOMContentLoaded", () => {\n  const h1 = document.querySelector("h1");\n  console.log(h1); // Now it exists\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"DOMContentLoaded"})," happens when the browser has finished reading the HTML and has built the DOM, meaning the tree of elements on the page.\nFrom that moment the elements exist: you can select them with ",(0,r.jsx)(n.code,{children:"querySelector"}),", read values from inputs, and modify text, classes, or attributes."]}),"\n",(0,r.jsxs)(n.p,{children:["There is one important detail: if the page contains ",(0,r.jsx)(n.code,{children:"defer"})," or ",(0,r.jsx)(n.code,{children:'type="module"'})," scripts, ",(0,r.jsx)(n.code,{children:"DOMContentLoaded"})," also waits for their execution.\nThe browser considers the DOM ready only after it has read the HTML and after it has run those scripts."]}),"\n",(0,r.jsxs)(n.p,{children:["There is also another event, ",(0,r.jsx)(n.code,{children:"window.onload"}),", often mentioned in old JavaScript examples.\n",(0,r.jsx)(n.code,{children:"window"})," is the global object of the browser; ",(0,r.jsx)(n.code,{children:".onload"})," is an event handler connected to the complete loading of the page.\nUnlike ",(0,r.jsx)(n.code,{children:"DOMContentLoaded"}),", the ",(0,r.jsx)(n.code,{children:"load"})," event arrives later because it also waits for external page resources, for example images, CSS files, fonts, and JavaScript files."]}),"\n",(0,r.jsxs)(n.p,{children:["To manipulate the DOM you almost never need to wait for all of this, ",(0,r.jsx)(n.code,{children:"DOMContentLoaded"})," is enough, or better yet ",(0,r.jsx)(n.code,{children:"defer"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Rule:"})," ",(0,r.jsx)(n.code,{children:"<script defer>"})," in the head as the default choice.\n",(0,r.jsx)(n.code,{children:'<script type="module">'})," has automatic defer.\n",(0,r.jsx)(n.code,{children:"async"})," only for scripts independent from the DOM.\n",(0,r.jsx)(n.code,{children:"DOMContentLoaded"})," as the historical solution if you cannot use defer."]}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)(n.h3,{id:"20-dom-manipulation-reading-and-modifying-the-page",children:"20. DOM Manipulation (Reading and Modifying the Page)"}),"\n",(0,r.jsxs)(n.p,{children:["As we saw, the ",(0,r.jsx)(n.strong,{children:"DOM"})," (Document Object Model) is the tree of objects the browser builds from your HTML.\nIt is what we introduced in the ",(0,r.jsx)(i.A,{to:"/docs/vademecum/html-real-world-vademecum#what-happens-when-you-open-a-page",children:"HTML Vademecum section about what happens when you open a page"}),", but here we will see it from JavaScript's point of view.\nManipulating the DOM means accessing that tree to read elements, change their text, modify classes, update attributes, or insert new nodes."]}),"\n",(0,r.jsx)(n.h4,{id:"selecting-elements",children:"Selecting Elements"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"document.getElementById()"})," searches by ",(0,r.jsx)(n.code,{children:"id"})," and returns the element (or ",(0,r.jsx)(n.code,{children:"null"}),").\nIt is the most direct choice when you already know the element id: it does not need to interpret a CSS selector and it is faster than ",(0,r.jsx)(n.code,{children:'querySelector("#id")'}),", even though in practice we are talking about minimal performance differences, with almost always negligible impact.\nIt is useful mainly because it c
1ommunicates intention better: if you are searching for an element by ",(0,r.jsx)(n.code,{children:"id"}),", ",(0,r.jsx)(n.code,{children:"getElementById"})," says exactly that."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const header = document.getElementById("header");\n// Searches for the element with id="header"\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"document.querySelector()"})," accepts a ",(0,r.jsx)(n.strong,{children:"CSS selector"})," and returns the ",(0,r.jsx)(n.strong,{children:"first"})," matching element.\nIt is more flexible: you can search by id, class, tag, attribute, or more complex combinations.\nUse it when you need the flexibility of CSS selectors."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'document.querySelector("#header");             // By id\ndocument.querySelector(".card");               // By class (first match)\ndocument.querySelector("button");              // By tag (first match)\ndocument.querySelector("nav > a");             // Complex selectors\ndocument.querySelector(\'[data-role="admin"]\'); // By attribute\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"document.querySelectorAll()"})," returns ",(0,r.jsx)(n.strong,{children:"all"})," matching elements, in a ",(0,r.jsx)(n.code,{children:"NodeList"}),", meaning a list of DOM nodes."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const buttons = document.querySelectorAll("button");\n// NodeList of all buttons on the page\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"htmlcollection-vs-nodelist-live-vs-static",children:[(0,r.jsx)(n.code,{children:"HTMLCollection"})," vs ",(0,r.jsx)(n.code,{children:"NodeList"})," (Live vs Static)"]}),"\n",(0,r.jsxs)(n.p,{children:["Methods like ",(0,r.jsx)(n.code,{children:"getElementsByClassName"})," return an ",(0,r.jsx)(n.code,{children:"HTMLCollection"}),", meaning a list of DOM elements.\nThis list is ",(0,r.jsx)(n.strong,{children:"live"}),": it stays connected to the DOM and updates automatically when the DOM changes.\nMethods like ",(0,r.jsx)(n.code,{children:"querySelectorAll"})," instead return a ",(0,r.jsx)(n.strong,{children:"static"})," ",(0,r.jsx)(n.code,{children:"NodeList"}),", meaning a snapshot of the situation at the moment you created it."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// Suppose there are 3 elements with class="card" at the beginning\nconst liveList = document.getElementsByClassName("card");\nconst staticList = document.querySelectorAll(".card");\n\n// Add a new card to the DOM\nconst newCard = document.createElement("div");\nnewCard.className = "card";\ndocument.body.appendChild(newCard);\n\nconsole.log(liveList.length);   // 4: includes the new card\nconsole.log(staticList.length); // 3: initial snapshot\n'})}),"\n",(0,r.jsxs)(n.p,{children:["A live list may seem convenient, but it creates subtle bugs: if you add or remove elements while you are using it, the list changes at that moment too.\nEven the value of ",(0,r.jsx)(n.code,{children:".length"})," can change during a loop.\nA static list is more predictable.\nIn modern practice, ",(0,r.jsxs)(n.strong,{children:["you almost always use ",(0,r.jsx)(n.code,{children:"querySelectorAll"})]}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["To iterate them with array methods like ",(0,r.jsx)(n.code,{children:".map"}),", convert them with spread.\nThe three dots ",(0,r.jsx)(n.code,{children:"..."})," take the elements of the ",(0,r.jsx)(n.code,{children:"NodeList"})," and insert them into a new array:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const cards = [...document.querySelectorAll(".card")];\nconst titles = cards.map(c => c.querySelector("h2").textContent);\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"properties-vs-methods",children:"Properties vs Methods"}),"\n",(0,r.jsxs)(n.p,{children:["On DOM elements you have two types of members: ",(0,r.jsx)(n.strong,{children:"properties"})," (nouns, without parentheses) and ",(0,r.jsx)(n.strong,{children:"methods"})," (verbs, with parentheses)."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const el = document.querySelector("#t
1itle");\n\n// Properties: read or assign\nel.textContent;         // Reads\nel.textContent = "New"; // Writes\nel.className = "active";\nel.id;\n\n// Methods: call with ()\nel.remove();\nel.addEventListener("click", handler);\nel.appendChild(other);\n'})}),"\n",(0,r.jsx)(n.p,{children:"A classic mistake is treating a property like a method or vice versa:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// \u274c WRONG\nel.textContent("New title"); // TypeError: textContent is not a function\n\n// \u2705 CORRECT\nel.textContent = "New title";\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"creating-and-modifying-elements",children:"Creating and Modifying Elements"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"document.createElement()"})," creates a new element, ",(0,r.jsx)(n.code,{children:"element.appendChild()"})," or ",(0,r.jsx)(n.code,{children:"element.append()"})," inserts it into the tree."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const newElement = document.createElement("div");\nnewElement.textContent = "Hello world";\nnewElement.className = "message";\n\ndocument.body.appendChild(newElement);\n// Added to the DOM: <div class="message">Hello world</div>\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:".append()"})," is more modern: it accepts multiple elements or text strings at the same time."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const list = document.querySelector("ul");\n\nconst first = document.createElement("li");\nfirst.textContent = "First";\n\nconst second = document.createElement("li");\nsecond.textContent = "Second";\n\nlist.append(first, second);\n// Two <li> elements are added at the end of the list\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"textcontent-vs-innerhtml-safe-vs-dangerous",children:[(0,r.jsx)(n.code,{children:"textContent"})," vs ",(0,r.jsx)(n.code,{children:"innerHTML"})," (Safe vs Dangerous)"]}),"\n",(0,r.jsx)(n.p,{children:"To change the content of an element you have two main properties, with a critical security difference."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const message = document.querySelector("#message");\n\nmessage.textContent = "Hello Mario";\n// Shows plain text\n\nmessage.innerHTML = "<strong>Hello</strong> Mario";\n// Inserts real HTML markup\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"textContent"})," treats the value as ",(0,r.jsx)(n.strong,{children:"plain text"}),".\nAny HTML character is shown literally, not interpreted."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'el.textContent = "<b>hello</b>";\n// On screen you see: <b>hello</b> (literally)\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"innerHTML"})," treats the value as ",(0,r.jsx)(n.strong,{children:"HTML"}),".\nTags and attributes are interpreted and inserted as real markup, meaning as the actual HTML structure of the page."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'el.innerHTML = "<b>hello</b>";\n// On screen you see: hello (bold)\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The problem with ",(0,r.jsx)(n.code,{children:"innerHTML"})," is that if you put ",(0,r.jsx)(n.strong,{children:"user input"})," inside without sanitizing it, someone can inject malicious code.\nSanitizing means cleaning or transforming a value before inserting it into the page, so the browser does not interpret it as code.\nThis type of attack is called ",(0,r.jsx)(n.strong,{children:"XSS"})," (Cross-Site Scripting), meaning the insertion of malicious scripts into a page seen by other users."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"// \u274c DANGEROUS: if userComment comes from the user\nconst userComment = '<img src=x onerror=\"stealCookies()\">';\nel.innerHTML = `They wrote: ${userComment}`;\n// The browser can execute code inserted through attributes like onerror\n"})}),"\n",(0,r.jsxs)(n.p,{children:["The rule is: ",(0,r.jsxs)(n.strong,{children:["use ",(0,r.jsx)(n.code,{children:"textContent"})," as the default choice"]}),", move to ",(0,r.jsx)(n.code,{children:"innerHTML"})," only when you are actually building markup and ",(0,r.jsx)(n.strong,{children:"only with content you control"})," (never unsanitized user input).\nTo handle user input with ",(0,r.jsx)(n.code,{children:"innerHTML"}),", we will see ",(0,r.jsx)(n.code,{children:"escapeHtml"})," and the difference between escaping and sanitization in ",(0,r.jsx)("a",{href:"#escape-html-and-sanitization-e
1scapehtml",children:"section 29"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"insertAdjacentHTML"})," lets you insert HTML in precise positions relative to an element (before, inside at the beginning, inside at the end, after), without touching the rest.\nIt is useful for adding markup without rebuilding everything, but it is still HTML interpreted by the browser, so the same cautions as ",(0,r.jsx)(n.code,{children:"innerHTML"})," apply if the content comes from the user."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'el.insertAdjacentHTML("beforeend", "<li>New</li>");\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"styles-style-vs-classlist",children:["Styles: ",(0,r.jsx)(n.code,{children:"style"})," vs ",(0,r.jsx)(n.code,{children:"classList"})]}),"\n",(0,r.jsxs)(n.p,{children:["Modifying style directly with ",(0,r.jsx)(n.code,{children:".style.property"})," works, but it mixes logic and presentation in JavaScript, which is why it is a practice to avoid when possible."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// \u274c Inline style, less maintainable\nel.style.backgroundColor = "red";\nel.style.padding = "10px";\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The preferable practice is to define CSS classes and ",(0,r.jsx)(n.strong,{children:"toggle classes"})," from JavaScript.\nAll styling stays in CSS, JavaScript only decides when to apply it.\nThis principle is called ",(0,r.jsx)(n.strong,{children:"separation of concerns"}),": CSS manages appearance, JavaScript decides when to apply or remove a class."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// \u2705 Add, remove, toggle, check\nel.classList.add("active");\nel.classList.remove("hidden");\nel.classList.toggle("selected");\nel.classList.contains("admin"); // true or false\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"classList.toggle"})," is particularly elegant: it adds the class if it is missing, removes it if it is there."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'button.addEventListener("click", () => {\n  menu.classList.toggle("open");\n});\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"data--as-a-bridge-between-html-and-js",children:[(0,r.jsx)(n.code,{children:"data-*"})," as a Bridge Between HTML and JS"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"data-*"})," attributes (seen in the ",(0,r.jsx)(i.A,{to:"/docs/vademecum/html-real-world-vademecum#12-global-attributes-universal-tools",children:"HTML Vademecum section about global attributes"}),") are the standard way to attach custom data to HTML elements. From JavaScript you read them through ",(0,r.jsx)(n.code,{children:"dataset"}),"."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-html",children:'<button data-action="delete" data-id="42">Delete</button>\n'})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const button = document.querySelector("button");\nbutton.dataset.action; // "delete"\nbutton.dataset.id;     // "42" (always a string, convert if you need a number)\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Hyphenated names become camelCase in ",(0,r.jsx)(n.code,{children:"dataset"}),": ",(0,r.jsx)(n.code,{children:"data-user-role"})," becomes ",(0,r.jsx)(n.code,{children:"dataset.userRole"}),"."]}),"\n",(0,r.jsx)(n.p,{children:"This pattern is used to connect HTML elements to JavaScript behaviors without hardcoding every variant by hand.\nImagine a page with many buttons that do different things: instead of recognizing them one by one in code, you put in the HTML the minimal data JavaScript needs to decide what to do."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-html",children:'<button data-action="copy" data-target="snippet-1">Copy</button>\n<button data-action="share" data-url="https://...">Share</button>\n<button data-action="delete" data-id="42">Delete</button>\n'})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'document.addEventListener("click", (e) => {\n  const button = e.target.closest("button[data-action]");\n  if (!button) return;\n\n  const action = button.dataset.action;\n  if (action === "copy") copy(button.dataset.target);\n  else if (action === "share") share(button.dataset.url);\n  else if (action === "delete") remove(button.dataset.id);\n});\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"navigatorclipboard-copying-to-the-clipboard",children:[(0,r.jsx)(n.code,{children:"navigator.clipboard"})," (Copying to the Clipboard)"]}),"\n",(0,r.jsxs)(n.p,{children:['To copy text to the clipboard from code (the classic "Copy" button next to a code snippet), the modern method is ',(0,r.jsx)(n.code,{children:"navigator.clipboard.writeText()"}),", asynchronous and Promise-based.\nA Promise represents a future result: copying may succeed or fail after you call the method."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'async function copyText(text) {\n  try {\n    await navigator.clipboard.writeText(text);\n    showToast("Copied to clipboard");\n  } catch (err) {\n    console.error("Copy failed:", err);\n  }\n}\n\nconst button = document.querySelector("#copy");\nbutton.addEventListener("click", () => {\n  const snippet = document.querySelector("#snippet").textContent;\n  copyText(snippet);\n});\n'})}),"\n",(0,r.jsx)(n.p,{children:"The browser requires the call to happen in response to a user action (a click) and in a secure context, usually HTTPS.\nThis prevents malicious sites from reading or modifying the clipboard without consent. Calling it outside an event handler usually fails."}),"\n",(0,r.jsxs)(n.p,{children:["There is also ",(0,r.jsx)(n.code,{children:"navigator.clipboard.readText()"})," to read from the clipboard, but it requires explicit user permission and does not work in insecure contexts (http or file)."]}),"\n",(0,r.jsxs)(n.p,{children:["On older browsers or contexts without HTTPS, the historical solution is ",(0,r.jsx)(n.code,{children:'document.execCommand("copy")'})," with a temporary text field.\nIn practice today modern browsers support ",(0,r.jsx)(n.code,{children:"navigator.clipboard"}),", and this solution is rarely needed."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Rule:"})," ",(0,r.jsx)(n.code,{children:"getElementById"})," for precise ids, ",(0,r.jsx)(n.code,{children:"querySelector"})," and ",(0,r.jsx)(n.code,{children:"querySelectorAll"})," for CSS selectors.\nProperties without ",(0,r.jsx)(n.code,{children:"(
1)"}),", methods with ",(0,r.jsx)(n.code,{children:"()"}),".\n",(0,r.jsx)(n.code,{children:"textContent"})," for plain text, ",(0,r.jsx)(n.code,{children:"innerHTML"})," only with controlled content.\n",(0,r.jsx)(n.code,{children:"classList"})," instead of direct ",(0,r.jsx)(n.code,{children:"style"}),".\n",(0,r.jsx)(n.code,{children:"data-*"})," + ",(0,r.jsx)(n.code,{children:"dataset"})," to connect HTML and JavaScript.\n",(0,r.jsx)(n.code,{children:"navigator.clipboard.writeText"})," to copy text from code."]}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)(n.h3,{id:"21-events-reacting-to-interactions",children:"21. Events (Reacting to Interactions)"}),"\n",(0,r.jsx)(n.p,{children:'Events are how the DOM communicates that something happened: a click, a pressed key, a submitted form, a changed input.\nYour code "listens" to these events and reacts.'}),"\n",(0,r.jsxs)(n.h4,{id:"addeventlistener-always-never-onclick",children:[(0,r.jsx)(n.code,{children:"addEventListener"})," (Always, Never ",(0,r.jsx)(n.code,{children:"onclick"}),")"]}),"\n",(0,r.jsxs)(n.p,{children:["The modern method for registering a listener is ",(0,r.jsx)(n.code,{children:"addEventListener"}),".\nA listener is a function that listens for an event.\n",(0,r.jsx)(n.code,{children:"addEventListener"})," accepts the event name and a callback function, meaning the function to call when the event happens."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const button = document.querySelector("button");\n\nbutton.addEventListener("click", () => {\n  console.log("Clicked!");\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The old way was assigning directly to ",(0,r.jsx)(n.code,{children:"element.onclick = function"}),", but it has a major flaw, namely that you can have only one handler at a time.\nAssigning a new ",(0,r.jsx)(n.code,{children:"onclick"})," overwrites the previous one.\n",(0,r.jsx)(n.code,{children:"addEventListener"}),", instead, can register as many listeners as you want, and all of them are called."]}),"\n",(0,r.jsxs)(n.p,{children:["Also, ",(0,r.jsx)(n.code,{children:"addEventListener"})," gives you ",(0,r.jsx)(n.code,{children:"removeEventListener"})," to remove a specific listener, but you must pass ",(0,r.jsx)(n.strong,{children:"the same function reference"})," you used to add it.\nThis is a place where the classic mistake happens:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// Reference vs execution\nbutton.addEventListener("click", handleClick()); // \u274c ERROR: you are calling the function and passing the result\nbutton.addEventListener("click", handleClick);   // \u2705 You pass the reference\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Parentheses ",(0,r.jsx)(n.code,{children:"()"})," execute the function.\nWhen you register a listener, you want to ",(0,r.jsx)(n.strong,{children:"pass the reference"}),", not run it immediately."]}),"\n",(0,r.jsxs)(n.p,{children:["The same principle applies when you want to remove a listener: an anonymous function written twice is not the same function, even if the code inside is identical.\nAnonymous means without a name and without a variable that stores it.\nEvery time you write ",(0,r.jsx)(n.code,{children:"() => { ... }"}),", you are creating a new function."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// \u274c Does not work: they are two different functions\nbutton.addEventListener("click", () => console.log("Clicked"));\nbutton.removeEventListener("click", () => console.log("Clicked"));\n'})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'function handleClick() {\n  console.log("Clicked");\n}\n\nbutton.addEventListener("click", handleClick);\nbutton.removeEventListener("click", handleClick); // Works: same reference\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"the-event-object",children:["The ",(0,r.jsx)(n.code,{children:"event"})," Object"]}),"\n",(0,r.jsxs)(n.p,{children:["The callback receives an ",(0,r.jsx)(n.code,{children:"event"})," object (conventionally called ",(0,r.jsx)(n.code,{children:"e"}),") with information about the event that just happened.\nInside that object you find, for example, which element generated the event, what type of event it was, and whether the user was holding special keys."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'button.addEventListener("click", (e) => {\n  console.log(e.target);    // For example <button>Save</button>
1, the element that generated the event\n  console.log(e.type);      // "click"\n  console.log(e.timeStamp); // About 1243.52, milliseconds since the page started\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"e.target"})})," is the concrete element where the event started.\nUseful for event delegation, which we will see shortly.\nDo not confuse it with ",(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"e.currentTarget"})}),", which is the element where you registered the listener.\nThey can be different if you click a child element and the event bubbles up to the parent."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-html",children:'<button id="save">\n  <span>Save</span>\n</button>\n'})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const button = document.getElementById("save");\n\nbutton.addEventListener("click", (e) => {\n  console.log(e.target);        // For example <span>Save</span>, if you clicked the text\n  console.log(e.currentTarget); // <button id="save">...</button>, where you registered the listener\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"e.preventDefault()"})})," blocks the browser's default behavior.\nThe most common case is form submission: normally the browser reloads the page, ",(0,r.jsx)(n.code,{children:"preventDefault"})," prevents that so you can handle the submit with JavaScript."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'form.addEventListener("submit", (e) => {\n  e.preventDefault(); // Blocks the page refresh\n  // Here you can read form data and handle submission with JavaScript\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"e.stopPropagation()"})})," blocks event propagation to parents.\nIt is useful when a click on an inner element must not also activate the listener of the container."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const card = document.querySelector(".card");\nconst deleteButton = document.querySelector(".delete");\n\ncard.addEventListener("click", () => {\n  openCardDetails();\n});\n\ndeleteButton.addEventListener("click", (e) => {\n  e.stopPropagation(); // The click does not reach the card\n  deleteElement();\n});\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"event-delegation-one-listener-for-many-elements",children:"Event Delegation (One Listener for Many Elements)"}),"\n",(0,r.jsxs)(n.p,{children:["The DOM supports ",(0,r.jsx)(n.strong,{children:"event propagation"}),': when you click an element, the event "bubbles up" from children to parents.\nIf you click an icon inside a button, the event starts from the icon, passes to the button, then to the upper containers.\nThis behavior enables ',(0,r.jsx)(n.strong,{children:"delegation"}),": one listener on a parent handles the events of all children."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-html",children:'<ul id="list">\n  <li><button data-id="1">Delete 1</button></li>\n  <li><button data-id="2">Delete 2</button></li>\n  <li><button data-id="3">Delete 3</button></li>\n  \x3c!-- Potentially hundreds of buttons --\x3e\n</ul>\n'})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// \u274c One listener for every button (inefficient if there are many)\ndocument.querySelectorAll("button").forEach(b => {\n  b.addEventListener("click", () => remove(b.dataset.id));\n});\n\n// \u2705 One listener on the parent\ndocument.querySelector("#list").addEventListener("click", (e) => {\n  const button = e.target.closest("button");\n  if (!button) return; // The click was not on a button\n  remove(button.dataset.id);\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:["There are two concrete advantages.\nThe first is ",(0,r.jsx)(n.strong,{children:"performance"}),": one listener instead of one for every button.\nThe second is ",(0,r.jsx)(n.strong,{children:"dynamic behavior"}),": if you add new buttons after loading, they work automatically without registering new listeners."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:".closest()"})," is fundamental in this pattern: starting from ",(0,r.jsx)(n.code,{children:"e.target"}),", it climbs the DOM tree looking for the first element that matches the selector.\nIf the click happened on an icon inside the button, ",(0,r.jsx)(n.code,{children:"e.target"})," is the icon; ",(0,r.jsx)(n.code,{children:'closest("button")'})," climbs to the parent button."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"common-events",children:"Common Events"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Keyboard events"}
1),": ",(0,r.jsx)(n.code,{children:"keydown"})," starts when the key is pressed, and it is the most used one for shortcuts and actions like Escape or Enter.\n",(0,r.jsx)(n.code,{children:"keyup"})," starts when the key is released.\n",(0,r.jsx)(n.code,{children:"keypress"})," used to be used for typed characters, but it is deprecated because it does not handle non-textual keys like Escape, Delete, or arrows well, and it had inconsistent behavior across browsers."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'document.addEventListener("keydown", (e) => {\n  if (e.key === "Escape") closeMenu();\n  if (e.key === "Enter" && e.ctrlKey) submit();\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"e.key"})," contains the name of the pressed key, for example ",(0,r.jsx)(n.code,{children:'"Escape"'}),", ",(0,r.jsx)(n.code,{children:'"Enter"'}),", or ",(0,r.jsx)(n.code,{children:'"a"'}),".\n",(0,r.jsx)(n.code,{children:"e.ctrlKey"}),", ",(0,r.jsx)(n.code,{children:"e.shiftKey"}),", ",(0,r.jsx)(n.code,{children:"e.altKey"}),", and ",(0,r.jsx)(n.code,{children:"e.metaKey"})," are booleans that tell you whether those modifier keys were pressed at the same time.\n",(0,r.jsx)(n.code,{children:"metaKey"})," is usually Cmd on Mac and the Windows key on Windows."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Input events"}),": ",(0,r.jsx)(n.code,{children:"change"}
1)," is generated when the input loses focus after a change (or on selection for dropdown menus).\n",(0,r.jsx)(n.code,{children:"input"})," is generated ",(0,r.jsx)(n.strong,{children:"on every typed character"})," in real time."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// change: only when the user "confirms"\nfield.addEventListener("change", (e) => {\n  save(e.target.value);\n});\n\n// input: on every typed character, for live search\nfield.addEventListener("input", (e) => {\n  filter(e.target.value);\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Submit"}),": generated when a form is submitted.\nPair it with ",(0,r.jsx)(n.code,{children:"e.preventDefault()"})," to handle submission from JavaScript."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'form.addEventListener("submit", (e) => {\n  e.preventDefault();\n  const data = new FormData(form);\n  sendData(data);\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"FormData"})," is the standard API for collecting all fields of a form.\nIt takes the ",(0,r.jsx)(n.code,{children:"<form>"})," element and produces an iterable object with ",(0,r.jsx)(n.code,{children:"name -> value"})," pairs, where ",(0,r.jsx)(n.code,{children:"name"})," is the ",(0,r.jsx)(n.code,{children:"name"})," attribute of each input."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const form = document.querySelector("form");\nconst data = new FormData(form);\n\ndata.get("email");      // "[email protected]"\ndata.get("password");   // "..."\ndata.has("newsletter"); // true/false\n\nfor (const [key, value] of data) {\n  console.log(key, value);\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["To send it with ",(0,r.jsx)(n.code,{children:"fetch"}),", pass the instance directly as ",(0,r.jsx)(n.code,{children:"body"}),".\nThe browser automatically sets the correct ",(0,r.jsx)(n.code,{children:"Content-Type"})," (including ",(0,r.jsx)(n.code,{children:"multipart/form-data"})," for files)."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'form.addEventListener("submit", async (e) => {\n  e.preventDefault();\n  const res = await fetch("/api/send", {\n    method: "POST",\n    body: new FormData(form),\n  });\n  if (res.ok) showSuccess();\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:["If you want to convert the data into a classic JavaScript object, use ",(0,r.jsx)(n.code,{children:"Object.fromEntries"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const object = Object.fromEntries(new FormData(form));\n// { email: "...", password: "...", newsletter: "on" }\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Notice that inputs without ",(0,r.jsx)(n.code,{children:"name"})," are not included in ",(0,r.jsx)(n.code,{children:"FormData"}),".\nIf a field does not appear, check that it has the ",(0,r.jsx)(n.code,{children:"name"})," attribute in the HTML tag."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Pointer events"}),": ",(0,r.jsx)(n.code,{children:"pointerdown"})," and ",(0,r.jsx)(n.code,{children:"pointerup"})," are used to handle interactions with mouse, touch, and pen using the same events.\nThey are useful for immediate visual feedback, like button press state, which ",(0,r.jsx)(n.code,{children:"click"})," cannot provide because it fires only on release."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'button.addEventListener("pointerdown", () => {\n  button.classList.add("pressed");\n});\n\nbutton.addEventListener("pointerup", () => {\n  button.classList.remove("pressed");\n});\n\nbutton.addEventListener("pointerleave", () => {\n  button.classList.remove("pressed");\n});\n\nbutton.addEventListener("pointercancel", () => {\n  button.classList.remove("pressed");\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"pointerleave"})," and ",(0,r.jsx)(n.code,{children:"pointercancel"})," prevent the button from visually staying pressed if the pointer leaves the element or if the browser interrupts the interaction."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"the-this-problem-with-addeventlistener-in-classes",children:["The ",(0,r.jsx)(n.code,{children:"this"})," Problem with ",(0,r.jsx)(n.code,{children:"addEventListener"})," in Classes"]}
1),"\n",(0,r.jsxs)(n.p,{children:["As we saw in ",(0,r.jsx)(i.A,{to:"/docs/vademecum/javascript-real-world/oop-and-modules#this-the-reference-to-the-instance",children:"section 13"}),", inside classes ",(0,r.jsx)(n.code,{children:"this"})," is lost when you pass a method as a callback.\nThe simplest solution is to register a wrapper with an arrow function, so the method is called through the correct instance."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'class Counter {\n  constructor(button) {\n    this.count = 0;\n    // \u2705 The arrow wrapper preserves this\n    button.addEventListener("click", () => this.increment());\n  }\n\n  increment() {\n    this.count++;\n  }\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Rule:"})," always ",(0,r.jsx)(n.code,{children:"addEventListener"}),", never ",(0,r.jsx)(n.code,{children:"onclick"}),".\nPass the function reference, not the call.\nUse event delegation for lists of elements.\n",(0,r.jsx)(n.code,{children:"closest()"})," is your ally in delegation."]}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsxs)(n.h3,{id:"22-dialogs-and-modals-the-dialog-tag",children:["22. Dialogs and Modals (The ",(0,r.jsx)(n.code,{children:"<dialog>"})," Tag)"]}),"\n",(0,r.jsxs)(n.p,{children:["Before 2022, creating a modal meant custom CSS and JavaScript: dark overlays, focus traps, Escape handling, body overflow.\nA focus trap is the behavior where the Tab key stays inside the modal, instead of moving to the elements behind it.\nHTML now has ",(0,r.jsx)(n.code,{children:"<dialog>"}),", a native element that manages many of these behaviors."]}),"\n",(0,r.jsx)(n.h4,{id:"basic-structure",children:"Basic Structure"}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"<dialog>"})," tag is a container hidden by default.\nYou show it with the ",(0,r.jsx)(n.code,{children:".show()"})," methods (non-modal) or ",(0,r.jsx)(n.code,{children:".showModal()"})," (modal, blocks the rest of the page), and close it with ",(0,r.jsx)(n.code,{children:".close()"}),"."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-html",children:'<dialog id="confirmDialog">\n  <h2>Are you sure?</h2>\n  <p>This action is irreversible.</p>\n  <form method="dialog">\n    <button value="cancel">Cancel</button>\n    <button value="confirm">Confirm</button>\n  </form>\n</dialog>\n\n<button id="open">Delete</button>\n'})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const dialog = document.getElementById("confirmDialog");\nconst open = document.getElementById("open");\n\nopen.addEventListener("click", () => dialog.showModal());\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"showmodal-vs-show",children:[(0,r.jsx)(n.code,{children:"showModal()"})," vs ",(0,r.jsx)(n.code,{children:"show()"})]}),"\n",(0,r.jsx)(n.p,{children:"The difference is substantial."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:".show()"})," shows the dialog as a normal element on the page.\nThe rest of the content stays interactive, you can click outside, no overlay."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:".showModal()"})," shows the dialog as ",(0,r.jsx)(n.strong,{children:"modal"}),": it is placed above everything through the browser's ",(0,r.jsx)(n.strong,{children:"top layer"}),", meaning a special layer above the normal page.\nThe rest of the page is inactive and dimmed by a ",(0,r.jsx)(n.code,{children:"::backdrop"}),", a pseudo-element you can style via CSS, and focus is trapped inside the dialog.\nPressing ",(0,r.jsx)(n.code,{children:"Escape"})," closes it automatically."]}),"\n",(0,r.jsxs)(n.p,{children:["For a real modal, you will almost always use ",(0,r.jsx)(n.code,{children:"showModal()"}),".\n",(0,r.jsx)(n.code,{children:"show()"})," is for specific cases where you want to show a panel without blocking the rest of the page."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"closing-the-dialog-and-returnvalue",children:["Closing the Dialog and ",(0,r.jsx)(n.code,{children:"returnValue"})]}),"\n",(0,r.jsx)(n.p,{children:"You have three possible ways to close the dialog:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"dialog.close(value)"})})," in JavaScript, with an optional value that ends up in ",(0,r.jsx)(n.code,{children:"dialog.returnValue"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsxs)(n.strong,{children:["Button with ",(0,r.jsx)(n.code,{children:'form method="dialog"'})]})," inside the dialog: clicking it closes the dialog and uses the button ",(0,r.jsx)(n.code,{children:"value"})," as returnValue"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Escape key"})," (works only with ",(0,r.jsx)(n.code,{children:"showModal"}),")"]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:'form method="dialog"'})," pattern is elegant because it avoids registering a handler on every button."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-html",children:'<dialog id="askName">\n  <form method="dialog">\n    <input name="name" type="text" autofocus />\n    <button value="cancel">Cancel</button>\n    <button value="ok">OK</button>\n  </form>\n</dialog>\n'})}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const dialog = document.getElementById("askName");\ndialog.showModal();\n\ndialog.addEventListener("close", () => {\n  console.log("ReturnValue:", dialog.returnValue); // "cancel" or "ok"\n  if (dialog.returnValue === "ok") {\n    const name = dialog.querySelector("input[name=\'name\']").value;\n    console.log("Entered name:", name);\n  }\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"close"}
1)," event is generated when the dialog is closed.\n",(0,r.jsx)(n.code,{children:"returnValue"})," is a string saved on the dialog at closing time: it contains the ",(0,r.jsx)(n.code,{children:"value"})," of the clicked button or the value you passed to ",(0,r.jsx)(n.code,{children:"dialog.close(value)"}),'.\nIt is useful when you want to know why the dialog was closed, for example "cancel" or "ok".']}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"built-in-accessibility",children:"Built-In Accessibility"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"<dialog>"})," with ",(0,r.jsx)(n.code,{children:"showModal()"})," includes behaviors that otherwise you would need to implement by hand:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"Focus trap: Tab and Shift+Tab stay inside the dialog"}),"\n",(0,r.jsxs)(n.li,{children:["Initial focus on the first focusable element (or on one with ",(0,r.jsx)(n.code,{children:"autofocus"}),")"]}),"\n",(0,r.jsx)(n.li,{children:"Escape to close"}),"\n",(0,r.jsxs)(n.li,{children:["Implicit ARIA ",(0,r.jsx)(n.code,{children:"dialog"})," role for screen readers"]}),"\n",(0,r.jsx)(n.li,{children:"The rest of the page is made inactive while the modal is open"}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["For this reason ",(0,r.jsx)(n.code,{children:"<dialog>"})," is preferable to modals built only with ",(0,r.jsx)(n.code,{children:"div"}),", CSS, and manual listeners, when the use case is a true modal."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Rule:"})," use ",(0,r.jsx)(n.code,{children:"<dialog>"})," for confirmation modals, details, and overlay forms.\n",(0,r.jsx)(n.code,{children:"showModal()"})," for almost all cases.\n",(0,r.jsx)(n.code,{children:'form method="dialog"'})," to close with ",(0,r.jsx)(n.code,{children:"returnValue"})," without explicit handlers."]}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"asynchrony",children:"Asynchrony"}),"\n",(0,r.jsx)(n.h3,{id:"23-synchronous-vs-asynchronous",children:"23. Synchronous vs Asynchronous"}),"\n",(0,r.jsxs)(n.p,{children:["JavaScript is ",(0,r.jsx)(n.strong,{children:"single-threaded"}),', meaning it executes one operation at a time, in sequence.\nA thread is a flow of execution; saying "single-threaded" therefore means that the main JavaScript code does not do two things at the exact same moment.\nThis may seem limiting (does a slow operation block everything?), but JavaScript solves the problem with the ',(0,r.jsx)(n.strong,{children:"asynchronous"})," model: potentially slow operations (network calls, timers, reading/writing to storage or databases, file access) are delegated to the system, and the code keeps running.\nWhen the operation finishes, JavaScript comes back to handle it."]}),"\n",(0,r.jsx)(n.h4,{id:"synchronous-example",children:"Synchronous Example"}),"\n",(0,r.jsx)(n.p,{children:"Synchronous code runs one line at a time, in order, blocking everything until each operation finishes."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'console.log("start");\nconst result = calculateSomething(); // Blocks until the result\nconsole.log("end");\n'})}),"\n",(0,r.jsx)(n.p,{children:"This is fine for quick operations (calculations, string manipulation) but disastrous for slow operations."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"the-problem-slow-operations",children:"The Problem: Slow Operations"}),"\n",(0,r.jsx)(n.p,{children:"Imagine having to download data from a server.\nIf it were synchronous, the code would stay still for seconds waiting for the response.\nDuring those seconds the browser would be frozen: scroll, clicks, keyboard input, animations, and visual updates would remain stuck until the operation finishes."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"// Synchronous pseudocode (fortunately JS does not work like this)\nconst data = downloadFromServer(); // Blocks 2 seconds\nupdateUI(data);\n// For 2 seconds the user sees a frozen page\n"})}),"\n",(0,r.jsxs)(n.p,{children:["The solution is ",(0,r.jsx)(n.strong,{children:"asynchronous"}),': we start the operation, but we do not stay there waiting.\nIt is as if we told it "when you are done, call me" and continued doing something else.']}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"the-three-forms-of-async",children:"The Three Forms of Async"}),"\n",(0,r.jsx)(n.p,{children:"JavaScript has evolved how it handles async over time.\nToday three forms coexist, in historical order:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Callbacks"})," (historical, still used)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Promises"})," (ES6, 2015)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.code,{children:"async"}),"/",(0,r.jsx)(n.code,{children:"await"})]})," (ES2017, the modern standard)"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:'They are layers over the same basic mechanism: the code says "when it finishes, do this", and meanwhile continues with something else.'}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Callbacks"}),": you pass a function that will be called when the operation completes."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'setTimeout(() =>
1 {\n  console.log("2 seconds have passed");\n}, 2000);\nconsole.log("This appears first");\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Promise"}),": the operation returns an object that represents a future result, and with ",(0,r.jsx)(n.code,{children:".then()"})," you say what to do when that result arrives."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'downloadFromServer().then(data => {\n  updateUI(data);\n});\nconsole.log("This appears first");\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"async/await"}),": you write code that ",(0,r.jsx)(n.strong,{children:"looks"})," synchronous, but does not actually block.\nIt is the modern and clean syntax."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"async function loadData() {\n  const data = await downloadFromServer();\n  updateUI(data);\n}\n"})}),"\n",(0,r.jsx)(n.p,{children:"All three ways lead to the same result.\nIn the next sections we will see Promises and fetch in detail, then async/await."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"the-event-loop-how-it-really-works",children:"The Event Loop (How It Really Works)"}),"\n",(0,r.jsxs)(n.p,{children:["The reason JavaScript can be single-threaded but still handle slow operations without blocking is called the ",(0,r.jsx)(n.strong,{children:"Event Loop"}),".\nUnderstanding it conceptually will help you understand why certain behaviors are what they are."]}),"\n",(0,r.jsx)(n.p,{children:"To understand the model, start from two main structures."}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.strong,{children:"Call Stack"}),' is the stack of functions currently running.\n"Stack" means pile: the last function that enters is the first one that exits.\nWhen you call a function, it is put on top.\nWhen it finishes, it is removed.\nSynchronous code runs entirely here, and as long as the stack is not empty, nothing else can run.']}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.strong,{children:"Task Queue"}),' (or Callback Queue) is a queue of callbacks waiting to run.\n"Queue" really means line: the first callback that is ready is the first one taken.\nWhen you call ',(0,r.jsx)(n.code,{children:"setTimeout"})," or register an event listener, the callback does not go immediately into the stack: it waits in a queue managed by the browser (or by Node.js).\nThe browser takes care of the timer or event in the background, and when it is time, puts the callback in the Task Queue.\nPromise callbacks, including those coming from ",(0,r.jsx)(n.code,{children:"fetch().then(...)"}),", instead end up in the Microtask Queue, which we will see shortly."]}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.strong,{children:"Event Loop"})," is the mechanism that connects the Call Stack to the work queues.\nIt constantly checks the situation: if the Call Stack is empty, it takes the next job from the queue and puts it in the stack to run it."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'console.log("A");\n\nsetTimeout(() => {\n  console.log("B");\n}, 0);\n\nconsole.log("C");\n\n// Output: A, C, B\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Even with a ",(0,r.jsx)(n.code,{children:"0"})," millisecond timer, ",(0,r.jsx)(n.code,{children:"B"})," comes out last, because ",(0,r.jsx)(n.code,{children:'console.log("A")'})," and ",(0,r.jsx)(n.code,{children:'console.log("C")'})," run on the Call Stack synchronously.\n",(0,r.jsx)(n.code,{children:"setTimeout"})," puts its callback in the queue; the Event Loop will run it ",(0,r.jsx)(n.strong,{children:"only when the Call Stack is empty"}),", meaning after the synchronous code has finished."]}),"\n",(0,r.jsxs)(n.p,{children:["There is also a higher-priority ",(0,r.jsx)(n.strong,{children:"Microtask Queue"}),", where Promise callbacks (",(0,r.jsx)(n.code,{children:".then"}),", ",(0,r.jsx)(n.code,{children:".catch"}),", ",(0,r.jsx)(n.code,{children:"await"}),") go.\nMicrotasks are small jobs that JavaScript must complete as soon as possible after the current synchronous code.\nMicrotasks always run before ordinary tasks.\nThis explains why a ",(0,r.jsx)(n.code,{children:"Promise.resolve().then(fn)"})," runs before a ",(0,r.jsx)(n.code,{children:"setTimeout(fn, 0)"}),', even if both look "immediate".']}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'console.log("A");\n\nsetTimeout(() => console.log("B"), 0);\nPromise.resolve().then(() => console.log("C"));\n\nconsole.log("D");\n\n// Output: A, D, C, B\n// D before C and B because it is synchronous.\n// C before B because Promises go to the Microtask Queue (high priority).\n'})}
1),"\n",(0,r.jsxs)(n.p,{children:["The practical implication: ",(0,r.jsx)(n.strong,{children:"never do heavy operations synchronously"}),".\nA calculation that takes one second blocks the Call Stack, and for that second animations, clicks, and queued callbacks remain stuck.\nThe user would see a frozen page.\nHeavy operations should be split into smaller parts with ",(0,r.jsx)(n.code,{children:"setTimeout"}),", or moved to a ",(0,r.jsx)("a",{href:"#web-workers-notes",children:"Web Worker"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Rule:"})," JavaScript is single-threaded but asynchronous.\nThe Event Loop runs callbacks from the Task Queue when the Call Stack is empty.\nMicrotasks (Promise) come before tasks (setTimeout).\nNever block the Call Stack with heavy synchronous operations.\nUse ",(0,r.jsx)(n.code,{children:"async"}),"/",(0,r.jsx)(n.code,{children:"await"})," as the default syntax in modern code.\nCallbacks remain for events and timers."]}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsxs)(n.h3,{id:"24-promises-and-fetch",children:["24. Promises and ",(0,r.jsx)(n.code,{children:"fetch"})]}),"\n",(0,r.jsxs)(n.p,{children:["A ",(0,r.jsx)(n.strong,{children:"Promise"})," is an object that represents a future value.\nIt is not the value itself: it represents a value that will arrive later, or an error if the operation fails."]}),"\n",(0,r.jsx)(n.h4,{id:"the-three-states",children:"The Three States"}),"\n",(0,r.jsx)(n.p,{children:"A Promise can be in one of these three states:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"pending"}),": the operation is in progress"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"fulfilled"}),": the operation succeeded, there is a value"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"rejected"}),": the operation failed, there is an error"]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:['A Promise starts as pending and ends as fulfilled or rejected.\nOnce resolved, it does not go back.\nWhen we say that a Promise "resolves", we mean that it stops waiting and reaches a final state: success (',(0,r.jsx)(n.code,{children:"fulfilled"}),") or error (",(0,r.jsx)(n.code,{children:"rejected"}),")."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"then-catch-finally",children:[(0,r.jsx)(n.code,{children:".then()"}),", ",(0,r.jsx)(n.code,{children:".catch()"}),", ",(0,r.jsx)(n.code,{children:".finally()"})]}),"\n",(0,r.jsx)(n.p,{children:"You can attach handlers to the three states with these three methods."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'downloadData()\n  .then(data => {\n    console.log("Success:", data);\n  })\n  .catch(error => {\n    console.error("Failed:", error);\n  })\n  .finally(() => {\n    console.log("Finished (both on success and error)");\n  });\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:".then()"})," receives the value on success.\n",(0,r.jsx)(n.code,{children:".catch()"})," receives the error on failure.\n",(0,r.jsx)(n.code,{children:".finally()"})," always runs, typically for cleanup operations like hiding a spinner or re-enabling a button."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"promise-chain",children:"Promise Chain"}),"\n",(0,r.jsxs)(n.p,{children:["Every ",(0,r.jsx)(n.code,{children:".then()"})," returns a new Promise, so you can chain them.\nThe value returned by one ",(0,r.jsx)(n.code,{children:".then()"})," becomes the input of the next one."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"downloadUser(id)\n  .then(user => downloadOrders(user.id))\n  .then(orders =>
1 downloadDetails(orders[0].id))\n  .then(details => showDetails(details))\n  .catch(error => showError(error));\n"})}),"\n",(0,r.jsxs)(n.p,{children:["A single ",(0,r.jsx)(n.code,{children:".catch()"})," at the end handles any error in the chain.\nIf the first ",(0,r.jsx)(n.code,{children:"downloadUser"})," fails, the flow jumps directly to ",(0,r.jsx)(n.code,{children:".catch()"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["The readability of the chain is good as long as the flow stays simple, but it gets much worse when the logic becomes complicated with conditions, loops, or multiple asynchronous operations.\nIn those cases ",(0,r.jsx)(n.code,{children:"async"}),"/",(0,r.jsx)(n.code,{children:"await"})," makes the flow more linear."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"fetch-http-calls",children:[(0,r.jsx)(n.code,{children:"fetch()"})," (HTTP Calls)"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"fetch()"})," is the modern API for making HTTP requests.\nAn HTTP request is communication with a server, to ask for or send data.\n",(0,r.jsx)(n.code,{children:"fetch"})," returns a Promise that resolves with a ",(0,r.jsx)(n.code,{children:"Response"})," object, meaning the response received from the server."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'fetch("https://api.example.com/users")\n  .then(res => res.json())\n  .then(data => console.log(data))\n  .catch(err => console.error(err));\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The process has ",(0,r.jsx)(n.strong,{children:"two phases"}),":"]}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:["First you receive the ",(0,r.jsx)(n.strong,{children:"HTTP response"})," (the ",(0,r.jsx)(n.code,{children:"Response"})," object): it contains status code, headers, and a body that has not yet been read"]}),"\n",(0,r.jsxs)(n.li,{children:["Then you call ",(0,r.jsx)(n.code,{children:".json()"})," (or ",(0,r.jsx)(n.code,{children:".text()"}),") on the response to read and interpret the body, meaning the real content of the response, and this operation also returns ",(0,r.jsx)(n.strong,{children:"another Promise"})]}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["The status code is the number that describes the outcome of the response (",(0,r.jsx)(n.code,{children:"200"}),", ",(0,r.jsx)(n.code,{children:"404"}),", ",(0,r.jsx)(n.code,{children:"500"}),").\nHeaders are response metadata, for example format, cache, or authentication."]}),"\n",(0,r.jsx)(n.p,{children:"This separation may seem cumbersome at first, but it has a reason: for large responses, you can decide not to read the body (saving memory) or read it as a stream, meaning in chunks instead of all at once."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"the-trap-fetch-does-not-reject-on-http-errors",children:["The Trap: ",(0,r.jsx)(n.code,{children:"fetch"})," Does Not Reject on HTTP Errors"]}),"\n",(0,r.jsxs)(n.p,{children:["This is the most famous ",(0,r.jsx)(n.code,{children:"fetch"})," trap.\nA ",(0,r.jsx)(n.code,{children:".catch()"})," catches only network errors, such as failed DNS, lost connection, or blocked CORS.\nDNS is the system that translates a domain like ",(0,r.jsx)(n.code,{children:"example.com"})," into the server address.\nCORS is the security mechanism by which the browser decides whether a page can read responses from a different origin.\n",(0,r.jsx)(n.strong,{children:"It does not catch HTTP errors like 404 or 500"}),": for ",(0,r.jsx)(n.code,{children:"fetch"}),", the response arrived, just with a status code different from 200."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// \u274c You THINK you are handling errors, but this does not catch 404 or 500\nfetch("/api/does-not-exist")\n  .then(res => res.json())\n  .then(data => console.log(data))\n  .catch(err => console.error("Error:", err));\n// If the server responds 404, the .then() still runs\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The solution is to manually check ",(0,r.jsx)(n.code,{children:"res.ok"}),", which is ",(0,r.jsx)(n.code,{children:"true"})," only if the status is between 200 and 299."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// \u2705 CORRECT\nfetch("/api/users")\n  .then(res =>
1 {\n    if (!res.ok) {\n      throw new Error(`HTTP ${res.status}: ${res.statusText}`);\n    }\n    return res.json();\n  })\n  .then(data => console.log(data))\n  .catch(err => console.error("Error:", err));\n'})}),"\n",(0,r.jsxs)(n.p,{children:["If ",(0,r.jsx)(n.code,{children:"res.ok"})," is ",(0,r.jsx)(n.code,{children:"false"}),", we throw an explicit error that will make the flow jump to ",(0,r.jsx)(n.code,{children:".catch()"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"methods-other-than-get",children:"Methods Other than GET"}),"\n",(0,r.jsxs)(n.p,{children:["By default, ",(0,r.jsx)(n.code,{children:"fetch"})," performs a GET.\nFor POST, PUT, DELETE, pass a configuration object as the second argument."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'fetch("/api/users", {\n  method: "POST",\n  headers: {\n    "Content-Type": "application/json",\n  },\n  body: JSON.stringify({ name: "Mario", age: 27 }),\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:["With JSON, the body must be a string and you get it with ",(0,r.jsx)(n.code,{children:"JSON.stringify"}),".\nIn other cases ",(0,r.jsx)(n.code,{children:"fetch"})," also accepts other body types, for example ",(0,r.jsx)(n.code,{children:"FormData"}),", ",(0,r.jsx)(n.code,{children:"Blob"}),", or ",(0,r.jsx)(n.code,{children:"URLSearchParams"}),".\nHeaders depend on the format and authentication you use: with JSON you often set ",(0,r.jsx)(n.code,{children:'"Content-Type": "application/json"'}),", while with ",(0,r.jsx)(n.code,{children:"FormData"})," you usually let the browser handle it."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"building-urls-with-url-and-urlsearchparams",children:["Building URLs with ",(0,r.jsx)(n.code,{children:"URL"})," and ",(0,r.jsx)(n.code,{children:"URLSearchParams"})]}),"\n",(0,r.jsxs)(n.p,{children:["Concatenating strings to build URLs is fragile: you can forget to encode special characters, and a parameter with a space or ",(0,r.jsx)(n.code,{children:"&"})," can break everything.\nJavaScript has two native APIs to do it safely."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"URL"})," represents a complete URL and gives you properties to read and modify its parts."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const url = new URL("https://api.example.com/search");\nurl.searchParams.set("q", "modern javascript");\nurl.searchParams.set("limit", 10);\n\nconsole.log(url.toString());\n// "https://api.example.com/search?q=modern+javascript&limit=10"\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"URLSearchParams"})," specifically manages the query string.\nYou can use it by itself if you are interpreting an existing query."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const params = new URLSearchParams("q=javascript&limit=10");\nparams.get("q");         // "javascript"\nparams.has("limit");     // true\nparams.set("limit", 20);\nparams.toString();       // "q=javascript&limit=20"\n'})}),"\n",(0,r.jsxs)(n.p,{children:["In practice you pair them with ",(0,r.jsx)(n.code,{children:"fetch"})," to build requests with dynamic parameters."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'function search(query, page = 1) {\n  const url = new URL("/api/search", window.location.origin);\n  url.searchParams.set("q", query);\n  url.searchParams.set("p", page);\n  return fetch(url);\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"window.location.origin"})," is the origin of the current site, for example ",(0,r.jsx)(n.code,{children:"https://example.com"}),".\nThis lets you build an absolute URL from a relative path like ",(0,r.jsx)(n.code,{children:"/api/search"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["Spaces, accents, and ",(0,r.jsx)(n.code,{children:"&"})," inside values are encoded automatically.\nYou no longer need to manage this encoding by hand."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"abortcontroller-canceling-a-fetch",children:[(0,r.jsx)(n.code,{children:"AbortController"})," (Canceling a ",(0,r.jsx)(n.code,{children:"fetch"}),")"]}),"\n",(0,r.jsxs)(n.p,{children:["When the user starts a search and then changes their mind before the response arrives, the request is still in progress.\nIn critical cases, for example a live search with many calls or a page that is closed/changed, you want to be able to cancel the request from the browser side.\n",(0,r.jsx)(n.code,{children:"AbortController"})," is the standard mechanism."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const controller = new AbortController();\n\nfetch("/api/data", { signal: controller.signal })\n  .then(res => res.json())\n  .then(data => console.log(data))\n  .catch(err => {\n    if (err.name === "AbortError") {\n      console.log("Request canceled");\n    } else {\n      console.error(err);\n    }\n  });\n\n// Later, cancel the request\ncontroller.abort();\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"abort()"})," cancels the request from the browser's point of view and makes ",(0,r.jsx)(n.code,{children:"fetch"})," fail with an ",(0,r.jsx)(n.code,{children:"AbortError"}),".\nIt does not necessarily mean the server immediately stops working: if the request has already arrived, the server may still process it, write data, or complete the response."]}),"\n",(0,r.jsxs)(n.p,{children:["The classic pattern is combining ",(0,r.jsx)(n.code,{children:"AbortController"})," with debounce for live search: when a new query arrives, cancel the previous one if it has not resolved yet."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'let 
1currentController = null;\n\nasync function search(query) {\n  if (currentController) currentController.abort();\n  currentController = new AbortController();\n\n  try {\n    const url = new URL("/api/search", window.location.origin);\n    url.searchParams.set("q", query);\n\n    const res = await fetch(url, {\n      signal: currentController.signal,\n    });\n    if (!res.ok) throw new Error(`HTTP ${res.status}`);\n    showResults(await res.json());\n  } catch (err) {\n    if (err.name !== "AbortError") console.error(err);\n  }\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Without ",(0,r.jsx)(n.code,{children:"AbortController"}),", in a fast live-search scenario you risk responses arriving out of order and the displayed result being the one from the old query instead of the current one."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Rule:"})," ",(0,r.jsx)(n.code,{children:"fetch"})," returns a Promise with two phases (Response -> interpreted body).\nAlways check ",(0,r.jsx)(n.code,{children:"res.ok"})," because ",(0,r.jsx)(n.code,{children:"fetch"})," is not rejected on 4xx/5xx.\nUse ",(0,r.jsx)(n.code,{children:".catch()"})," at the end of the chain to handle errors.\n",(0,r.jsx)(n.code,{children:"URL"})," and ",(0,r.jsx)(n.code,{children:"URLSearchParams"})," to build requests without concatenating strings by hand.\n",(0,r.jsx)(n.code,{children:"AbortController"})," to cancel requests that are no longer needed."]}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsxs)(n.h3,{id:"25-asyncawait-async-that-looks-synchronous",children:["25. ",(0,r.jsx)(n.code,{children:"async"}),"/",(0,r.jsx)(n.code,{children:"await"})," (Async That Looks Synchronous)"]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"async"}),"/",(0,r.jsx)(n.code,{children:"await"})," is syntactic sugar over Promises, meaning a more readable syntax that still uses the same mechanism, so it does not change the behavior, but ",(0,r.jsx)(n.strong,{children:"how you write it"}),".\nThe result is asynchronous code that looks synchronous: readable from top to bottom, with ",(0,r.jsx)(n.code,{children:"await"}),' that "waits" for a Promise to resolve and returns the value.']}),"\n",(0,r.jsx)(n.h4,{id:"basic-syntax",children:"Basic Syntax"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"async function loadUser(id) {\n  const res = await fetch(`/api/users/${id}`);\n  const user = await res.json();\n  return user;\n}\n"})}),"\n",(0,r.jsxs)(n.p,{children:["The function is declared ",(0,r.jsx)(n.code,{children:"async"}),", which means it always returns a Promise, even when the code appears to return a value directly.\n",(0,r.jsx)(n.code,{children:"await"})," can be used only inside an ",(0,r.jsx)(n.code,{children:"async"})," function."]}),"\n",(0,r.jsx)(n.p,{children:"Compare it with the Promise-chain version:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"function loadUser(id) {\n  return fetch(`/api/users/${id}`)\n    .then(res => res.json())\n    .then(user => user);\n}\n"})}),"\n",(0,r.jsxs)(n.p,{children:["Both work, both return a Promise.\n",(0,r.jsx)(n.code,{children:"async"}),"/",(0,r.jsx)(n.code,{children:"await"})," is easier to read, especially when the logic becomes complex."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"handling-errors-with-trycatch",children:["Handling Errors with ",(0,r.jsx)(n.code,{children:"try"}),"/",(0,r.jsx)(n.code,{children:"catch"})]}),"\n",(0,r.jsxs)(n.p,{children:["With ",(0,r.jsx)(n.code,{children:"async"}),"/",(0,r.jsx)(n.code,{children:"await"}),", error handling uses the classic ",(0,r.jsx)(n.code,{children:"try"}),"/",(0,r.jsx)(n.code,{children:"catch"}),"."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'async function loadUser(id) {\n  try {\n    const res = await fetch(`/api/users/${id}`);\n    if (!res.ok) throw new Error(`HTTP ${res.status}`);\n    const user = await res.json();\n    return user;\n  } catch (error) {\n    console.error("Loading error:", error);\n    return null;\n  }\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["This is the biggest advantage of ",(0,r.jsx)(n.code,{children:"async"}),"/",(0,r.jsx)(n.code,{children:"await"}),": error handling works the same way in synchronous and asynchronous code.\nBefore, with Promises, you had to remember ",(0,r.jsx)(n.code,{children:".catch()"})," at the end; with ",(0,r.jsx)(n.code,{children:"async"}),"/",(0,r.jsx)(n.code,{children:"await"})," you use the same ",(0,r.jsx)(n.code,{children:"try"}),"/",(0,r.jsx)(n.code,{children:"catch"})," as the rest of the language."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"two-error-levels",children:"Two Error Levels"}),"\n",(0,r.jsx)(n.p,{children:"In practice, you want to handle errors in two different ways depending on who reads them."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"For you as developer"}),": log the error to the console with as much detail as possible.\nYou will see it during debugging."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"For the user"}),": show an understandable message in the UI.\nThey do not care about the stack trace, meaning the technical list of calls that led to the error, they care about what to do."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'async function submitForm(data) {\
1n  try {\n    const res = await fetch("/api/send", {\n      method: "POST",\n      headers: { "Content-Type": "application/json" },\n      body: JSON.stringify(data),\n    });\n    if (!res.ok) throw new Error(`HTTP ${res.status}`);\n    return await res.json();\n  } catch (error) {\n    // For the developer\n    console.error("Form submission error:", error);\n    // For the user\n    showToast("Something went wrong. Try again shortly.");\n    throw error; // Rethrow if the caller needs it\n  }\n}\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"await-without-async-is-an-error",children:[(0,r.jsx)(n.code,{children:"await"})," Without ",(0,r.jsx)(n.code,{children:"async"})," Is an Error"]}),"\n",(0,r.jsxs)(n.p,{children:["Remember: ",(0,r.jsx)(n.code,{children:"await"})," is allowed ",(0,r.jsxs)(n.strong,{children:["only inside ",(0,r.jsx)(n.code,{children:"async"})," functions"]}),".\nIn modern modules it can also be used in the main body of the module, behavior called ",(0,r.jsx)(n.strong,{children:"top-level await"}),".\nOutside these cases, it is a syntax error."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// \u274c WRONG\nfunction loadUser() {\n  const res = await fetch("/api/users"); // SyntaxError\n}\n\n// \u2705 CORRECT\nasync function loadUser() {\n  const res = await fetch("/api/users");\n}\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"parallel-operations-with-promiseall",children:["Parallel Operations with ",(0,r.jsx)(n.code,{children:"Promise.all"})]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"await"})," runs sequentially: it waits for the first, then the second, then the third.\nIf the operations are ",(0,r.jsx)(n.strong,{children:"independent"}),", you can run them in parallel with ",(0,r.jsx)(n.code,{children:"Promise.all"}),", which accepts an array of Promises and returns an array of results when all are complete."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// \u274c Sequential: waits 3 x requestTime total\nasync function loadEverythingSequential() {\n  const user = await fetch("/api/user").then(r => r.json());\n  const orders = await fetch("/api/orders").then(r => r.json());\n  const messages = await fetch("/api/messages").then(r => r.json());\n  return { user, orders, messages };\n}\n\n// \u2705 Parallel: waits only for the slowest one\nasync function loadEverythingParallel() {\n  const [user, orders, messages] = await Promise.all([\n    fetch("/api/user").then(r => r.json()),\n    fetch("/api/orders").then(r => r.json()),\n    fetch("/api/messages").then(r => r.json()),\n  ]);\n  return { user, orders, messages };\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:["If one of the Promises fails, the whole ",(0,r.jsx)(n.code,{children:"Promise.all"})," goes rejected, meaning it fails immediately and moves to the ",(0,r.jsx)(n.code,{children:"catch"}),".\nThe other operations that already started are not canceled automatically: the overall result is simply no longer considered successful.\nIf you want to wait for all of them to finish, both on success and error, use ",(0,r.jsx)(n.code,{children:"Promise.allSettled"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"custom-error-classes",children:"Custom Error Classes"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"Error"})," is a class like the others, so you can extend it to create error types specific to your domain.\nThe advantage is being able to distinguish errors with ",(0,r.jsx)(n.code,{children:"instanceof"})," and act differently depending on the type."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'class NetworkError extends Error {\n  constructor(message, statusCode) {\n    super(message);\n    this.name = "NetworkError";\n    this.statusCode = statusCode;\n  }\n}\n\nclass ValidationError extends Error {\n  constructor(message, field) {\n    super(message);\n    this.name = "ValidationError";\n    this.field = field;\n  }\n}\n'})}
1),"\n",(0,r.jsx)(n.p,{children:"Then in code that can fail:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'async function submitForm(data) {\n  if (!data.email.includes("@")) {\n    throw new ValidationError("Invalid email", "email");\n  }\n\n  const res = await fetch("/api/send", { /* ... */ });\n  if (!res.ok) {\n    throw new NetworkError("Submission failed", res.status);\n  }\n\n  return await res.json();\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:"And the caller decides how to handle each type:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'try {\n  await submitForm(data);\n  showSuccess();\n} catch (err) {\n  if (err instanceof ValidationError) {\n    highlightField(err.field, err.message);\n  } else if (err instanceof NetworkError) {\n    showToast(`Server error (${err.statusCode})`);\n  } else {\n    console.error("Unexpected error:", err);\n    showToast("Something went wrong");\n  }\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:"Without custom error classes, the alternative is comparing strings in messages (a fragile and hard-to-maintain solution).\nWith classes, handling is clean and scales well as the code grows."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Rule:"})," ",(0,r.jsx)(n.code,{children:"async"}),"/",(0,r.jsx)(n.code,{children:"await"})," as the default choice for modern asynchronous code.\n",(0,r.jsx)(n.code,{children:"try"}),"/",(0,r.jsx)(n.code,{children:"catch"})," for errors.\nTwo levels: console for you, UI for the user.\n",(0,r.jsx)(n.code,{children:"Promise.all"})," for independent operations in parallel.\nCustom error classes when you want to distinguish error types in the ",(0,r.jsx)(n.code,{children:"catch"}),"."]}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h2,{id:"storage-pwa-and-patterns",children:"Storage, PWA, and Patterns"}),"\n",(0,r.jsxs)(n.h3,{id:"26-storage-localstorage-and-indexeddb",children:["26. Storage (",(0,r.jsx)(n.code,{children:"localStorage"})," and ",(0,r.jsx)(n.code,{children:"IndexedDB"}),")"]}),"\n",(0,r.jsx)(n.p,{children:"Saving data that survives a page refresh is a common need: user preferences, form drafts, cart, app state.\nThe browser gives you two main APIs, suited to different cases."}),"\n",(0,r.jsx)(n.h4,{id:"localstorage",children:(0,r.jsx)(n.code,{children:"localStorage"})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"localStorage"})," is key-value storage, meaning it saves data by associating a name with a value.\nIt is synchronous (it blocks the code until the operation finishes) and persistent (it survives refresh and browser restart).\nFor ",(0,r.jsx)(n.code,{children:"localStorage"}),', browsers apply a practical limit of about 5 MB per origin.\nHere "origin" means the combination of protocol, domain, and port: ',(0,r.jsx)(n.code,{children:"https://example.com"})," and ",(0,r.jsx)(n.code,{children:"http://example.com"})," are different origins, as are ",(0,r.jsx)(n.code,{children:"https://example.com"})," and ",(0,r.jsx)(n.code,{children:"https://example.com:3000"}),"."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// Save\nlocalStorage.setItem("theme", "dark");\n\n// Read\nconst theme = localStorage.getItem("theme"); // "dark"\n\n// Remove\nlocalStorage.removeItem("theme");\n\n// Clear everything\nlocalStorage.clear();\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The most important thing to remember is that ",(0,r.jsxs)(n.strong,{children:[(0,r.jsx)(n.code,{children:"localStorage"})," stores only strings"]}),".\nTo save objects or arrays, turn them into strings with ",(0,r.jsx)(n.code,{children:"JSON.stringify"})," and rebuild them with ",(0,r.jsx)(n.code,{children:"JSON.parse"})," when reading."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// Save an object\nconst settings = { theme: "dark", language: "en", notifications: true };\nlocalStorage.setItem("settings", JSON.stringify(settings));\n\n// Read and rebuild\nconst data = JSON.parse(localStorage.getItem("settings"));\nconsole.log(data.theme); // "dark"\n'})}),"\n",(0,r.jsxs)(n.p,{children:["If the key does not exist, ",(0,r.jsx)(n.code,{children:"getItem"})," returns ",(0,r.jsx)(n.code,{children:"null"}),".\n",(0,r.jsx)(n.code,{children:"JSON.parse(null)"})," returns ",(0,r.jsx)(n.code,{children:"null"})," without error, so reading is safe even when nothing is saved.\nBut if the key exists with a value that is not val
1id JSON, ",(0,r.jsx)(n.code,{children:"JSON.parse"})," throws an error.\nIn practice, use ",(0,r.jsx)(n.code,{children:"try"}),"/",(0,r.jsx)(n.code,{children:"catch"})," when the data may not be valid JSON."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'function readSettings() {\n  try {\n    return JSON.parse(localStorage.getItem("settings")) ?? {};\n  } catch {\n    return {};\n  }\n}\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"when-to-use-localstorage",children:["When to Use ",(0,r.jsx)(n.code,{children:"localStorage"})]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"localStorage"})," is suitable for small, simple, non-sensitive data:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsx)(n.li,{children:"User preferences, such as theme, language, or preferred layout"}),"\n",(0,r.jsx)(n.li,{children:"Filters and sorting from the last search"}),"\n",(0,r.jsx)(n.li,{children:"Form drafts you want to recover after a refresh"}),"\n",(0,r.jsx)(n.li,{children:"Temporary cart or minimal app state"}),"\n",(0,r.jsx)(n.li,{children:"Open/closed panels, selected tab, or last visited page"}),"\n"]}),"\n",(0,r.jsxs)(n.p,{children:["Do not use it for sensitive data or authentication tokens: ",(0,r.jsx)(n.code,{children:"localStorage"})," is readable from JavaScript, so an XSS attack could read it."]}),"\n",(0,r.jsxs)(n.p,{children:["It is ",(0,r.jsx)(n.strong,{children:"synchronous"}),", so slow operations (saving MB of data) block the interface.\nAlso, the limit of about 5 MB is real: if you reach it, ",(0,r.jsx)(n.code,{children:"setItem"})," can throw a ",(0,r.jsx)(n.code,{children:"QuotaExceededError"}),"; if you do not handle it, the code stops at that point."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"indexeddb-storage-for-large-data",children:[(0,r.jsx)(n.code,{children:"IndexedDB"})," (Storage for Large Data)"]}),"\n",(0,r.jsxs)(n.p,{children:["When ",(0,r.jsx)(n.code,{children:"localStorage"})," is not enough (large data, files, image blobs), ",(0,r.jsx)(n.code,{children:"IndexedDB"})," is the choice.\nIt is a complete key-value database, asynchronous, with indexes and transactions, suitable for much larger amounts of data than ",(0,r.jsx)(n.code,{children:"localStorage"}),".\nThe actual quota depends on the browser, available space, and device, so it should not be treated as a fixed number.\nIndexes help search data quickly, transactions group operations that must succeed or fail together."]}),"\n",(0,r.jsxs)(n.p,{children:["The cost is complexity: the native API is verbose.\nIn practice people often use a wrapper, meaning a small library that makes the API more readable, but the important concept here is understanding when to move from ",(0,r.jsx)(n.code,{children:"localStorage"})," to ",(0,r.jsx)(n.code,{children:"IndexedDB"}),"."]}),"\n",(0,r.jsxs)(n.p,{children:["We will not go into the native ",(0,r.jsx)(n.code,{children:"IndexedDB"})," API in detail because it is a topic of its own.\nThe concept that matters is: when you need to save heavy data (files, images, PDFs), ",(0,r.jsx)(n.code,{children:"localStorage"})," is not enough, and ",(0,r.jsx)(n.code,{children:"IndexedDB"})," is the right tool."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"cookies-brief-mention",children:"Cookies (Brief Mention)"}),"\n",(0,r.jsx)(n.p,{children:"Cookies still exist but have a specific use case: data that must reach the server on every HTTP request, for example authentication with a session ID.\nThey are small (about 4 KB per cookie) and are automatically included in requests to the domain they belong to."}),"\n",(0,r.jsxs)(n.p,{children:["For client-side-only data, ",(0,r.jsxs)(n.strong,{children:["use ",(0,r.jsx)(n.code,{children:"localStorage"})," or ",(0,r.jsx)(n.code,{children:"IndexedDB"})]}),", not cookies.\nCookies have network overhead, meaning they slightly increase the weight of every request because they go to the server every time.\nThey also have security complications, such as ",(0,r.jsx)(n.strong,{children:"CSRF"})," (Cross-Site Request Forgery) attacks.\nA CSRF attack works precisely by exploiting automatic cookie sending.\nImagine being logged in to ",(0,r.jsx)(n.code,{children:"bank.example"}),": the browser keeps the session cookie.\nIf you then visit a malicious site, that site could try to 
1start a request toward ",(0,r.jsx)(n.code,{children:"bank.example"}),".\nIf the browser automatically attaches the cookie and the server does not correctly verify that the request truly comes from the legitimate page, the server may believe that the action comes from you."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Rule:"})," ",(0,r.jsx)(n.code,{children:"localStorage"})," for small, simple, non-sensitive data, with ",(0,r.jsx)(n.code,{children:"JSON.stringify"}),"/",(0,r.jsx)(n.code,{children:"parse"})," for objects.\n",(0,r.jsx)(n.code,{children:"IndexedDB"})," for large data.\nCookies only when the server must receive them on every request."]}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)(n.h3,{id:"27-service-workers-and-pwa-notes",children:"27. Service Workers and PWA (Notes)"}),"\n",(0,r.jsxs)(n.p,{children:["A ",(0,r.jsx)(n.strong,{children:"Service Worker"})," is a JavaScript script separate from the page.\nThe browser can start it when it needs to handle specific events and can use it to ",(0,r.jsx)(n.strong,{children:"intercept network requests"})," from your site.\nIt is not a script that is always running.\nThe browser activates it when needed, for example for a ",(0,r.jsx)(n.code,{children:"fetch"}),", ",(0,r.jsx)(n.code,{children:"push"}),", ",(0,r.jsx)(n.code,{children:"install"}),", or ",(0,r.jsx)(n.code,{children:"activate"})," event.\nIntercepting means it can put itself between the page and the network, deciding whether to respond with data from the cache or let the normal request start.\nThis unlocks capabilities typical of installed apps: offline behavior, controlled cache, push notifications, background sync, and managed updates."]}),"\n",(0,r.jsxs)(n.p,{children:["Service Workers are the heart of ",(0,r.jsx)(n.strong,{children:"PWA"})," (Progressive Web Apps), websites that behave like installable apps."]}),"\n",(0,r.jsx)(n.h4,{id:"what-a-service-worker-can-do",children:"What a Service Worker Can Do"}),"\n",(0,r.jsx)(n.p,{children:"The main capabilities are these:"}),"\n",(0,r.jsxs)(n.ol,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Intercept requests"}),": when the page asks for a CSS file, an image, or an API call, the service worker can see that request and decide whether to respond from cache, go to the network, or build a response."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Work offline"}),": if you have cached the essential files, the page can open even without connection."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Manage cache strategies"}),": you can treat static assets, HTML, and API data differently."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Receive background events"}),": in supported browsers and contexts, it can handle push notifications or deferred syncs."]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:"Manage updates"}),": it can install a new version and let you notify the user when it is ready."]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"registering-a-service-worker",children:"Registering a Service Worker"}),"\n",(0,r.jsxs)(n.p,{children:["Registration is done from the page code, for example in ",(0,r.jsx)(n.code,{children:"app.js"}),": the page tells the browser which file to use as the service worker."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// In the main page\nif ("serviceWorker" in navigator) {\n  navigator.serviceWorker.register("./sw.js")\n    .then(reg => console.log("SW registered:", reg.scope))\n    .catch(err => console.error("Registration failed:", err));\n}\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"sw.js"})," is the service worker file.\nIt is not executed like a normal page script: it runs in a separate context, without direct access to the DOM, and responds to specific events.\nThe general structure revolves around three key events:"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"install"})}
1),": happens when the service worker is installed, and it is the moment when you can cache the initial files"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"activate"})}),": happens when the service worker takes control, and it is the typical place to clean old caches"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.strong,{children:(0,r.jsx)(n.code,{children:"fetch"})}),": every request controlled by the service worker passes through here, and you can decide whether to respond from cache or from the network"]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const CACHE_NAME = "app-v1";\nconst INITIAL_ASSETS = ["/", "/styles.css", "/app.js"];\n\nself.addEventListener("install", (event) => {\n  event.waitUntil(\n    caches.open(CACHE_NAME).then(cache => cache.addAll(INITIAL_ASSETS))\n  );\n});\n\nself.addEventListener("activate", (event) => {\n  event.waitUntil(\n    caches.keys().then(cacheNames =>\n      Promise.all(\n        cacheNames\n          .filter(name => name !== CACHE_NAME)\n          .map(name => caches.delete(name))\n      )\n    )\n  );\n});\n\nself.addEventListener("fetch", (event) => {\n  event.respondWith(\n    caches.match(event.request).then(cachedResponse => {\n      return cachedResponse || fetch(event.request);\n    })\n  );\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"event.waitUntil()"})," tells the browser that the event is not finished until the Promise resolves.\n",(0,r.jsx)(n.code,{children:"event.respondWith()"})," tells the browser which response to use for that request.\nIn the code above, if a response is already in cache it is used immediately; otherwise the request goes to the network."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"cache-strategies",children:"Cache Strategies"}),"\n",(0,r.jsxs)(n.p,{children:["Cache strategies decide the order in which you try cache and network.\nThere is no one right strategy for everything: it depends on the resource type.\nThe examples below are meant for ",(0,r.jsx)(n.code,{children:"GET"})," requests, meaning read requests such as HTML, CSS, JavaScript, images, or public data.\nFor this reason the snippets ignore non-",(0,r.jsx)(n.code,{children:"GET"})," requests, such as ",(0,r.jsx)(n.code,{children:"POST"}),", because they should not be saved and reused as if they were static files."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Cache-first"}),": look in cache, if it is there use it; otherwise go to the network.\nIdeal for static assets, such as CDN libraries, icons, and fonts, that change rarely.\nA CDN is a network of servers used to distribute files quickly from different geographic points."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'self.addEventListener("fetch", (event) => {\n  if (event.request.method !== "GET") return;\n\n  event.respondWith(\n    caches.match(event.request).then(cachedResponse => {\n      if (cachedResponse) return cachedResponse;\n      return fetch(event.request);\n    })\n  );\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Network-first"}),": try the network, if it fails use the cache.\nIdeal for HTML, JSON, dynamic content that you need to keep fresh but where an offline response is better than an error."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'self.addEventListener("fetch", (event) => {\n  if (event.request.method !== "GET") return;\n\n  event.respondWith(\n    fetch(event.request).catch(async () => {\n      const cachedResponse = await caches.match(event.request);\n      if (cachedResponse) return cachedResponse;\n      throw new Error("No response available");\n    })\n  );\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Stale-while-revalidate"}),": serve from cache (immediately, fast), but meanwhile ask the network to update the cache for the next time.\nAn excellent compromise between speed and freshness."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'self.addEventListener("fetch", (event) => {\n  if (event.request.method !== "GET") return;\n\n  event.respondWith(\n    caches.open(CACHE_NAME).then(async (cache) => {\n      const cachedResponse = await cache.match(event.request);\n      const networkResponse = fetch(event.request).then((response) => {\n        if (response.ok) {\n          cache.put(event.request, response.clone());\n        }\n        return response;\n      });\n\n      return cachedResponse || networkResponse;\n    })\n  );\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:["There are also stricter strategies: ",(0,r.jsx)(n.strong,{children:"cache-only"}),", where you respond only from cache, and ",(0,r.jsx)(n.strong,{children:"network-only"}),", where you always pass through the network.\nThey are less common, but useful when you want very predictable behavior."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"detecting-updates",children:"Detecting Updates"}),"\n",(0,r.jsx)(n.p,{children:"An old Service Worker can keep serving an old version of the site while the page is open.\nWhen a new version is installed, it often waits until old tabs are closed or until you decide to reload the page.\nFor this reason many apps show a message when a new version is ready."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'navigator.serviceWorker.register("./sw.js").then(reg => {\n  reg.addEventListener("updatefound", () => {\n    const next = reg.installing;\n    next.addEventListener("statechange", () => {\n      if (next.state === "installed" && navigator.serviceWorker.controller) {\n        // A new version is ready, but the old one is still active\n        showUpdateToast();\n      }\n    });\n  });\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:['The typical toast says "Update available" with a "Reload" button that runs ',(0,r.jsx)(n.code,{children:"window.location.reload()"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"when-it-makes-sense-to-add-a-service-worker",children:"When It Makes Sense to Add a Service Worker"}),"\n",(0,r.jsx)(n.p,{children:"Service Workers are powerful but add complexity.\nIt makes sense to introduce a service worker when the site must work even with no connection or an unstable connection, when it uses heavy static assets that the user sees often, when you want to improve loads after the first visit, or when you are building an installable PWA with controlled updates."}),"\n",(0,r.jsx)(n.p,{children:"When you really need it, the MDN documentation on the Service Worker API is the best place to start."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Rule:"})," Service Worker for offline behavior, controlled cache, managed updates, and installable PWAs.\nRegister with ",(0,r.jsx)(n.code,{children:"navigator.serviceWorker.register"}),".\nHandle ",(0,r.jsx)(n.code,{children:"updatefound"})," to notify the user of new versions.\nDo not add it to a site that does not really need it."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"web-workers-notes",children:"Web Workers (Notes)"}),"\n",(0,r.jsxs)(n.p,{children:["A ",(0,r.jsx)(n.strong,{children:"Web Worker"})," is a script that runs in a separate thread, parallel to the main thread.\nThe main thread is the one that manages the page, events, and interface.\nIt does not manipulate the DOM (it has no access to it), but it can run heavy calculations without blocking the interface."]}),"\n",(0,r.jsx)(n.p,{children:"The typical use case: you have an expensive operation (interpreting a large file, compression, image processing, OCR, scientific calculations).\nIf you run it on the main thread, you block the Call Stack and the user sees a frozen page.\nIf you put it in a Worker, it runs in parallel and the UI stays responsive."}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// worker.js (separate file)\nself.addEventListener("message", (e) => {\n  const result = heavyCalculation(e.data);\n  self.postMessage(result);\n});\n\n// app.js\nconst worker = new Worker("./worker.js");\n\nworker.addEventListener("message", (e) => {\n  console.log("Result from worker:", e.data);\n});\n\nworker.postMessage({ input: "something" });\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Workers communicate with the main thread through messages (",(0,r.jsx)(n.code,{children:"postMessage"}),"), they cannot touch shared variables.\nThis is extremely positive for safety, because it avoids conflicts when accessing the same data, but it means passed data is copied or transferred."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Rule:"})," Service Worker for offline and cache, Web Worker for heavy calculations in parallel.\nB
1oth are specific tools, use them when the need is concrete."]}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)(n.h3,{id:"28-regular-expressions-regex",children:"28. Regular Expressions (Regex)"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Regular expressions"})," (regex) are a language within JavaScript for describing patterns in a string.\nThey let you validate input, extract portions of text, replace pieces, and split strings following a pattern.\nThe syntax is dense the first time, but once understood it enables operations that would otherwise require many lines of code."]}),"\n",(0,r.jsx)(n.h4,{id:"declaring-a-regex",children:"Declaring a Regex"}),"\n",(0,r.jsxs)(n.p,{children:["A regex is declared between two slashes ",(0,r.jsx)(n.code,{children:"/.../"})," or with the ",(0,r.jsx)(n.code,{children:"new RegExp()"})," constructor.\nAfter the final slash you can put ",(0,r.jsx)(n.strong,{children:"flags"})," that modify behavior."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const regex1 = /hello/;             // Literal\nconst regex2 = new RegExp("hello"); // Constructor (useful with variables)\n\n// With flags\nconst regex3 = /hello/i;  // i = case insensitive, does not distinguish uppercase/lowercase\nconst regex4 = /hello/g;  // g = global (finds all occurrences)\nconst regex5 = /hello/gi; // Combined\n'})}),"\n",(0,r.jsx)(n.p,{children:"The most common flags:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"i"})," (insensitive): does not distinguish uppercase and lowercase"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"g"})," (global): searches for all occurrences, does not stop at the first"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"m"})," (multiline): ",(0,r.jsx)(n.code,{children:"^"})," and ",(0,r.jsx)(n.code,{children:"$"})," match the start and end of each line, not only the whole string"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"u"})," (unicode): full Unicode support"]}),"\n"]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"the-methods",children:"The Methods"}),"\n",(0,r.jsxs)(n.p,{children:["To ",(0,r.jsx)(n.strong,{children:"check if there is a match"}),", use ",(0,r.jsx)(n.code,{children:".test()"})," on the regex (returns a boolean)."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'/hello/.test("hello world");  // true\n/hello/.test("good morning"); // false\n/hello/i.test("Hello");       // true (case insensitive)\n'})}),"\n",(0,r.jsxs)(n.p,{children:["To ",(0,r.jsx)(n.strong,{children:"extract the matching parts"}),", use ",(0,r.jsx)(n.code,{children:".match()"})," on the string (returns an array or ",(0,r.jsx)(n.code,{children:"null"}),")."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'"Hello world, hello everyone".match(/hello/gi);\n// ["Hello", "hello"]\n'})}),"\n",(0,r.jsxs)(n.p,{children:["To ",(0,r.jsx)(n.strong,{children:"replace"}),", use ",(0,r.jsx)(n.code,{children:".replace()"})," or ",(0,r.jsx)(n.code,{children:".replaceAll()"})," (as we saw in ",(0,r.jsx)(i.A,{to:"/docs/vademecum/javascript-real-world/foundations#essential-methods",children:"section 2"}),", but they also accept regex).\nWith a regex, ",(0,r.jsx)(n.code,{children:".replaceAll()"})," requires the ",(0,r.jsx)(n.code,{children:"g"})," flag, because it must know that you want to replace all occurrences."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'"hello world".replace(/world/, "universe"); // "hello universe"\n\n// With g flag, replaces all occurrences\n"aaa bbb aaa".replace(/aaa/g, "XXX");       // "XXX bbb XXX"\n"aaa bbb aaa".replaceAll(/aaa/g, "XXX");    // "XXX bbb XXX"\n'})}),"\n",(0,r.jsxs)(n.p,{children:["To ",(0,r.jsx)(n.strong,{children:"iterate over all matches with details"}),", use ",(0,r.jsx)(n.code,{children:".matchAll()"})," (with ",(0,r.jsx)(n.code,{children:"g"})," flag)."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'for (const match of "a1 b2 c3".matchAll(/([a-z])(\\d)/g)) {\n  console.log(match[0], match[1], match[2]);\n  // "a1" "a" "1"\n  // "b2" "b" "2"\n  // "c3" "c" "3"\n}\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"basic-syntax-1",children:"Basic Syntax"}),"\n",(0,r.jsx)(n.p,{children:"These are the primitives that compose any regex.\nMemorizing them can be difficult, so do not hesitate to consult this section when needed."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Literal characters"}),": match themselves."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'/hello/.test("hello"); // true\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Character classes"}),": match any one character from a set of characters, declared inside ",(0,r.jsx)(n.code,{children:"[]"}),"."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'/[aeiou]/.test("banana"); // true (there is a vowel)\n/[0-9]/.test("hello2");   // true (there is a digit)\n/[a-zA-Z]/.test("Hello"); // true (there is a letter)\n/[^0-9]/.test("123a");    // true (there is something that is NOT a digit, thanks to ^ inside)\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Shorthand classes"}),": shortcuts for common classes."]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"\\d"})," = digit (same as ",(0,r.jsx)(n.code,{children:"[0-9]"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"\\D"})," = non-digit"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"\\w"})," = ASCII letter, digit, or underscore (same as ",(0,r.jsx)(n.code,{children:"[A-Za-z0-9_]"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"\\W"})," = non-ASCII-letter, digit, or underscore"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"\\s"})," = whitespace (space, tab, newline)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"\\S"})," = non-whitespace"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"."})," = any character except newline"]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'/\\d{3}/.test("abc 123 def"); // true (three consecutive digits)\n/\\w+/.test("hello world");   // true (one or more letters/digits/underscores)\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Anchors"}),": position the match."]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"^"})," = start of string (or line, with flag ",(0,r.jsx)(n.code,{children:"m"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"$"})," = end of string (or line, with flag ",(0,r.jsx)(n.code,{children:"m"}),")"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"\\b"})," = word boundary"]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'/^hello/.test("hello world");
1 // true\n/^hello/.test("say hello");   // false (it is not at the start)\n\n/world$/.test("hello world"); // true\n/^hello$/.test("hello");      // true (the string is EXACTLY "hello")\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Quantifiers"}),": control how many times an element can appear."]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"*"})," = 0 or more times"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"+"})," = 1 or more times"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"?"})," = 0 or 1 time (optional)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"{n}"})," = exactly n times"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"{n,m}"})," = from n to m times"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"{n,}"})," = at least n times"]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'/\\d{4}/.test("year 2026"); // true (exactly 4 digits)\n/\\d{4,}/.test("123");      // false (less than 4 digits)\n/colou?r/.test("color");   // true (optional u, also matches "colour")\n/a+b/.test("aaab");        // true (a one or more times, followed by b)\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Groups and alternation"}),":"]}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"(...)"})," = group (captures, useful in ",(0,r.jsx)(n.code,{children:".match()"})," to extract)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"(?:...)"})," = non-capturing group"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"|"})," = alternation (OR)"]}),"\n"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'/(cat|dog)/.test("I have a dog");         // true (matches "dog")\n/^(hello|hi) world$/.test("hello world"); // true\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"common-use-patterns",children:"Common Use Patterns"}),"\n",(0,r.jsx)(n.p,{children:"Here are the patterns you will meet and reuse in real projects."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Validating an email"})," (simple version, suitable as a preliminary check in most forms):"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nemailRegex.test("[email protected]"); // true\nemailRegex.test("invalid");           // false\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"[^\\s@]+"}),' means "one or more characters that are not spaces or at signs".\n',(0,r.jsx)(n.code,{children:"^"})," and ",(0,r.jsx)(n.code,{children:"$"})," anchor to the whole string.\nThe pattern says: at least one character, one ",(0,r.jsx)(n.code,{children:"@"}),", at least one character, a dot, at least one character."]}),"\n",(0,r.jsx)(n.p,{children:"The full version according to RFC 5322 is much more complex and not very readable.\nIn practice, a simple regex like this combined with sending a confirmation email covers real cases."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Validating a phone number"})," (US format, only as an example of a more articulated regex):"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const phoneRegex = /^(1\\s?)?(\\(\\d{3}\\)|\\d{3})[\\s\\-]?\\d{3}[\\s\\-]?\\d{4}$/;\n\nphoneRegex.test("(555) 123-4567"); // true\nphoneRegex.test("555-123-4567");   // true\nphoneRegex.test("1 555 123 4567"); // true\nphoneRegex.test("555.123.4567");   // false (dots not handled)\n'})}),"\n",(0,r.jsx)(n.p,{children:"Breaking this regex down piece by piece:"}),"\n",(0,r.jsxs)(n.ul,{children:["\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"^(1\\s?)?"})," = optional country prefix: a ",(0,r.jsx)(n.code,{children:"1"})," followed by an optional space, all made optional by the final ",(0,r.jsx)(n.code,{children:"?"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"(\\(\\d{3}\\)|\\d{3})"})," = the area code: either 3 digits in parentheses ",(0,r.jsx)(n.code,{children:"(555)"})," or 3 digits without parentheses ",(0,r.jsx)(n.code,{children:"555"}),", thanks to alternation ",(0,r.jsx)(n.code,{children:"|"})]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"[\\s\\-]?"}
1)," = an optional separator (space or hyphen)"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"\\d{3}"})," = the first central block: exactly 3 digits"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"[\\s\\-]?"})," = another optional separator"]}),"\n",(0,r.jsxs)(n.li,{children:[(0,r.jsx)(n.code,{children:"\\d{4}$"})," = the last block: exactly 4 digits, anchored to the end of the string"]}),"\n"]}),"\n",(0,r.jsx)(n.p,{children:"If the regex is short, you do not need to comment it.\nIf instead it is long, critical, or not obvious, commenting the main pieces helps a lot; alternatively, consider more readable explicit code."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Extracting all links from text"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const text = "Go to https://example.com or http://another-site.org";\nconst links = text.match(/https?:\\/\\/[^\\s]+/g);\n// ["https://example.com", "http://another-site.org"]\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.code,{children:"https?"})," = ",(0,r.jsx)(n.code,{children:"http"})," with optional ",(0,r.jsx)(n.code,{children:"s"}),".\n",(0,r.jsx)(n.code,{children:"\\/\\/"})," = two literal slashes preceded by backslashes.\n",(0,r.jsx)(n.code,{children:"[^\\s]+"})," = one or more characters that are not spaces."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Cleaning multiple spaces"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'"hello    beautiful     world".replace(/\\s+/g, " ");\n// "hello beautiful world"\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Validating a strong password"})," (at least 8 characters, at least one uppercase letter, one lowercase letter, one digit):"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d).{8,}$/;\npasswordRegex.test("Password123"); // true\npasswordRegex.test("password");    // false (no uppercase, no digit)\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"(?=...)"})," parts are ",(0,r.jsx)(n.strong,{children:"lookaheads"}),': they check that something is present without "consuming" characters.\nIn practice, they look ahead in the string to verify a condition, but do not move the match position.\nThe pattern says: there must be a lowercase letter, there must be an uppercase letter, there must be a digit, and the total must be at least 8 characters.']}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Extracting parts with groups"}),":"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const date = "2026-04-14";\nconst match = date.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/);\n// match[0] = "2026-04-14" (full match)\n// match[1] = "2026"       (first group)\n// match[2] = "04"         (second group)\n// match[3] = "14"         (third group)\n'})}),"\n",(0,r.jsxs)(n.p,{children:["With ",(0,r.jsx)(n.strong,{children:"named groups"})," (modern, ES2018), you can give names to groups to read them explicitly:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'const match = "2026-04-14".match(/^(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})$/);\nmatch.groups.year;  // "2026"\nmatch.groups.month; // "04"\nmatch.groups.day;   // "14"\n'})}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"when-not-to-use-regex",children:"When Not to Use Regex"}),"\n",(0,r.jsx)(n.p,{children:"Regex are powerful, but they are not always the right choice."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Parsing HTML or JSON"}),": never do it with regex.\nHTML is recursively nested, meaning one tag can contain others, which in turn can contain still others.\nRegex cannot describe this structure reliably.\nUse ",(0,r.jsx)(n.code,{children:"DOMParser"})," for HTML and ",(0,r.jsx)(n.code,{children:"JSON.parse"})," for JSON."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Operations that a simple string can do"}),": if ",(0,r.jsx)(n.code,{children:".includes()"}),", ",(0,r.jsx)(n.code,{children:".startsWith()"}),", or ",(0,r.jsx)(n.code,{children:".split()"})," is enough, do not bring in a regex."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// \u274c Using a regex where it is not needed\n/hello/.test(string);\n\n// \u2705 Simpler and more readable\nstring.includes("hello");\n'})}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"When only the person who wrote it understands it"}),": if a regex becomes 200 characters long, you are probably building a parser (a mechanism that interprets a string according to a structure) that deserves to become real code.\nSo consider doing it step by step with ",(0,r.jsx)(n.code,{children:".split()"})," and explicit logic, it is slower but much more readable."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Rule:"})," ",(0,r.jsx)(n.code,{children:".test()"})," to check, ",(0,r.jsx)(n.code,{children:".match()"})," and ",(0,r.jsx)(n.code,{children:".matchAll()"})," to extract, ",(0,r.jsx)(n.code,{children:".replace()"})," to replace.\nUse the ",(0,r.jsx)(n.code,{children:"i"})," (case insensitive) and ",(0,r.jsx)(n.code,{children:"g"})," (global) flags according to the need.\nComment regex when they are long, critical, or not obvious.\nDo not parse HTML or JSON with regex.\nWhen in doubt, verify with a dedicated tool like regex101."]}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)(n.h3,{id:"29-essential-advanced-patterns",children:"29. Essential Advanced Patterns"}
1),"\n",(0,r.jsx)(n.p,{children:"Four patterns you will constantly use in real projects."}),"\n",(0,r.jsx)(n.h4,{id:"debounce-and-throttle",children:"Debounce and Throttle"}),"\n",(0,r.jsx)(n.p,{children:"Some events arrive quickly and repeatedly: scroll, resize, input that changes on every character.\nReacting to every event can be inefficient or harmful."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Debounce"}),": run the function ",(0,r.jsx)(n.strong,{children:"only after"})," the user has stopped generating the event for N milliseconds.\nIdeal for live search: you do not want to call the API on every key, you want to wait until the user has finished typing."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'function debounce(fn, ms) {\n  let timeoutId;\n  return function (...args) {\n    clearTimeout(timeoutId);\n    timeoutId = setTimeout(() => fn.apply(this, args), ms);\n  };\n}\n\n// Usage\nconst debouncedSearch = debounce((query) => {\n  const params = new URLSearchParams({ q: query });\n  fetch(`/api/search?${params}`).then(r => r.json()).then(showResults);\n}, 300);\n\ninput.addEventListener("input", (e) => debouncedSearch(e.target.value));\n// The user types "javascript" in 1 second\n// Only 1 server call, 300ms after the last key\n'})}),"\n",(0,r.jsxs)(n.p,{children:["While I write this page in Docusaurus, I see a similar effect: when I save the ",(0,r.jsx)(n.code,{children:".mdx"})," file, the change does not appear in the browser at the same instant, because the dev server must detect the change, recompile the affected page, and update the browser with hot reload, meaning an automatic update without manually reloading everything.\nIt is not exactly the same mechanism as debounce, but the practical idea is close: between a change and the visible result there is a small waiting time used to avoid continuous work and make the update manageable."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Throttle"}),": run the function ",(0,r.jsx)(n.strong,{children:"at most once every"})," N milliseconds.\nIdeal for scroll handlers: calculating on every frame would often be useless, while updating every 100ms can be enough to keep the interface smooth."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'function throttle(fn, ms) {\n  let lastRun = 0;\n  return function (...args) {\n    const now = Date.now();\n    if (now - lastRun >= ms) {\n      lastRun = now;\n      fn.apply(this, args);\n    }\n  };\n}\n\n// Usage\nconst scrollHandler = throttle(() => {\n  updateStickyHeader();\n}, 100);\n\nwindow.addEventListener("scroll", scrollHandler);\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The conceptual difference: debounce ",(0,r.jsx)(n.strong,{children:"waits for the end"})," (the user stops), throttle ",(0,r.jsx)(n.strong,{children:"limits frequency"})," (at most once every given time)."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsxs)(n.h4,{id:"escape-html-and-sanitization-escapehtml",children:["Escape HTML and Sanitization (",(0,r.jsx)(n.code,{children:"escapeHtml"}),")"]}),"\n",(0,r.jsxs)(n.p,{children:["When you insert user input into ",(0,r.jsx)(n.code,{children:"innerHTML"})," (which we saw is dangerous in ",(0,r.jsx)("a",{href:"#textcontent-vs-innerhtml-safe-vs-dangerous",children:"section 20"}),"), you must prevent the browser from interpreting it as HTML.\nFor simple text, you can do ",(0,r.jsx)(n.strong,{children:"HTML escaping"}),", meaning replacing special characters with their entities.\nThis way the browser shows the text literally instead of interpreting it as markup or code."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'function escapeHtml(string) {\n  return string\n    .replace(/&/g, "&amp;") // Must be first\n    .replace(/</g, "&lt;")\n    .replace(/>/g, "&gt;")\n    .replace(/"/g, "&quot;")\n    .replace(/\'/g, "&#39;");\n}\n\nconst userComment = \'<img src=x onerror="stealCookies()">\';\nel.innerHTML = `They wrote: ${escapeHtml(userComment)}`;\n// Shows the text literally, does not interpret the onerror attribute\n'})}),"\n",(0,r.jsxs)(n.p,{children:["The order matters: ",(0,r.jsx)(n.code,{children:"&"})," must be replaced first, otherwise it would replace the ",(0,r.jsx)(n.code,{children:"&"})," in the entities you introduce later."]}),"\n",(0,r.jsxs)(n.p,{children:["This is the manual foundation for showing text safely.\n",(0,r.jsx)(n.strong,{children:"Sanitization"})," is a broader step: it is needed when you want to accept real HTML but remove dangerous parts.\nIf you want to accept real HTML written by the user, escaping is not enough: you need full sanitization with dedicated and maintained tools.\nImplementing it by hand is risky, because dangerous cases include ",(0,r.jsx)(n.code,{children:"on*"})," attributes, ",(0,r.jsx)(n.code,{children:"javascript:"})," URLs, and HTML entities built to bypass superficial checks."]}),"\n",(0,r.jsxs)(n.p,{children:["As we saw in the ",(0,r.jsx)(i.A,{to:"/docs/vademecum/html-real-world-vademecum#11-html-entities-and-special-characters",children:"HTML Vademecum section about HTML entities"}),", this mechanism uses exactly the same entities: ",(0,r.jsx)(n.code,{children:"&lt;"}),", ",(0,r.jsx)(n.code,{children:"&gt;"}),", ",(0,r.jsx)(n.code,{children:"&amp;"}),"."]}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"configuration-driven-architecture",children:"Configuration-Driven Architecture"}),"\n",(0,r.jsxs)(n.p,{children:["A powerful organization pattern: ",(0,r.jsx)(n.strong,{children:"define behavior with data, not with code"}),".\nInstead of having a function that does one specific thing with an ",(0,r.jsx)(n.code,{children:"if"})," for every variant, you have a configuration object and a generic function that reads the configuration."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// \u274c Written directly in code: every variant requires changing the function\nfunction createForm(type) {\n  if (type === "contact") {\n    createField("Name", "text");\n    createField("Email", "email");\n    createField("Message", "textarea");\n  } else if (type === "signup") {\n    createField("Email", "email");\n    createField("Password", "password");\n  }\n  // Adding a new type = modifying the code\n}\n\n// \u2705 Configuration-driven: data describes, code executes\nconst FORM_CONFIG = {\n  contact: [\n    { label: "Name", type: "text" },\n    { label: "Email", type: "email" },\n    { label: "Message", type: "textarea" },\n  ],\n  signup: [\n    { label: "Email", type: "email" },\n    { label: "Password", type: "password" },\n  ],\n};\n\nfunction createForm(type) {\n  FORM_CONFIG[type].forEach(field => createField(field.label, field.type));\n}\n\n// Adding a type = adding an entry in FORM_CONFIG. Code unchanged.\n'})}),"\n",(0,r.jsx)(n.p,{children:"This pattern scales well because the code stays small and stable, variations grow in the configuration.\nIt is the principle behind many UI frameworks and libraries."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"immutability",children:"Immutability"}),"\n",(0,r.jsx)(n.p,{children:'Do not modify the original data, produce copies.\nAt first it may seem wasteful and you may wonder "why create a new array instead of modifying the one I have?", but it makes code more predictable and easier to debug.'}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"// \u274c Mutation: modifies the passed array\nfunction addProduct(cart, product) {\n  cart.push(product);
1 // Whoever passed the cart finds it modified\n  return cart;\n}\n\n// \u2705 Immutability: creates a new array\nfunction addProduct(cart, product) {\n  return [...cart, product];\n}\n"})}),"\n",(0,r.jsx)(n.p,{children:"For objects:"}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:"// \u2705 Updates a property by creating a new object\nfunction updateTheme(user, newTheme) {\n  return { ...user, theme: newTheme };\n}\n"})}),"\n",(0,r.jsx)(n.p,{children:"Immutability is a fundamental practice in React and similar frameworks: often the UI understands that something has changed by comparing object references, not by inspecting every internal property.\nIf you directly modify the same object or the same array, that detection becomes more fragile."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Rule:"}),' debounce for "wait until it ends", throttle for "limit the frequency".\n',(0,r.jsx)(n.code,{children:"escapeHtml"})," when you put user input in ",(0,r.jsx)(n.code,{children:"innerHTML"}),".\nConfiguration to scale without touching code.\nImmutability for predictable and testable code."]}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)(n.h3,{id:"30-testing-notes",children:"30. Testing (Notes)"}),"\n",(0,r.jsx)(n.p,{children:"Tests are automatic checks that you write once and run again every time you change the code, so you can immediately know whether a change broke an important behavior."}),"\n",(0,r.jsx)(n.p,{children:"Without tests, every important change must be verified by hand.\nAt first it seems faster, but as soon as the project grows you start losing time rechecking the same flows again and again."}),"\n",(0,r.jsx)(n.h4,{id:"two-types-of-tests",children:"Two Types of Tests"}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Unit tests"}),": test individual functions in isolation, are very fast (a few milliseconds), and precise (if they fail you know exactly where)."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// Function to test\nexport function calculateVat(price, rate = 0.22) {\n  return price * (1 + rate);\n}\n\n// Test with Vitest\nimport { expect, test } from "vitest";\nimport { calculateVat } from "./core.js";\n\ntest("calculateVat adds VAT to the price", () => {\n  expect(calculateVat(100)).toBe(122);\n  expect(calculateVat(100, 0.10)).toBe(110);\n});\n'})}),"\n",(0,r.jsxs)(n.p,{children:["Unit tests are where the pure functions vs DOM separation (",(0,r.jsx)(i.A,{to:"/docs/vademecum/javascript-real-world/oop-and-modules#18-separation-between-pure-functions-and-the-dom",children:"section 18"}),") pays off.\nA pure function is tested in one line, while one that touches the DOM requires an emulated browser environment."]}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"End-to-end tests (E2E)"}),": test the complete application flow in the browser by simulating the user, they are slower than unit tests, but verify UI, routing (meaning the code that decides which page or view to show), API, and application logic in the same flow."]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-javascript",children:'// Test with Playwright\nimport { test, expect } from "@playwright/test";\n\ntest("the user can add a product to the cart", async ({ page }) => {\n  await page.goto("http://localhost:3000");\n  await page.click("text=Add to cart");\n  await expect(page.locator("#cart-count")).toHaveText("1");\n});\n'})}),"\n",(0,r.jsx)(n.p,{children:"Playwright opens a real browser (headless by default, meaning without a visible window), loads the page, clicks, checks what it sees.\nIf something in the flow is broken (UI, API, logic), the test fails."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"the-right-tool-for-the-right-level",children:"The Right Tool for the Right Level"}),"\n",(0,r.jsx)(n.p,{children:"The testing pyramid is a common concept: many unit tests and few E2E tests."}),"\n",(0,r.jsx)(n.p,{children:"Pure core functions are the best place for unit tests: they have clear input and output, run quickly, and if they fail, they point to the problem precisely.\nCritical user flows, such as login, checkout, or registration, instead deserve E2E tests because they run in the browser like a user would run them.\nUI components are in the middle: you can test them with integration tests when behavior matters, or with visual regression when visual appearance is an important part of the result."}),"\n",(0,r.jsx)(n.p,{children:"For small projects, start with unit tests on the core.\nAdd E2E when critical flows deserve coverage.\nDo not try to test everything, instead focus on what breaks most often and what would be problematic if it broke."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"when-tests-are-not-needed",children:"When Tests Are Not Needed"}),"\n",(0,r.jsx)(n.p,{children:"The point is not having many tests, but having tests that check the right behavior.\nAgents and AI tools can generate many tests quickly, but a test that only verifies that a function exists, that it is called, or that a component renders without errors is of little use.\nA good test checks an observable result: given this input, I expect this output; after this click, I expect this change on the page."}),"\n",(0,r.jsx)(n.p,{children:"Tests pay off a lot on business rules, calculations, data transformations, reused modules, and flows you do not want to break.\nThey pay off less on UI experiments meant to change often, trivial glue code, or configurations that are already validated by tools."}),"\n",(0,r.jsx)(n.p,{children:"More and more often, agents are also used for exploratory tests and massive regressions, but even there they need clear direction: what must work, what must not break, what a real user must see."}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h4,{id:"typical-setup",children:"Typical Setup"}),"\n",(0,r.jsxs)(n.p,{children:["The setup changes depending on the stack: Vite, Next, Docusaurus, pure Node, and other environments have different c
1ommands and conventions.\nThe pattern, however, is almost always the same: you install test tools as development dependencies, add scripts in ",(0,r.jsx)(n.code,{children:"package.json"}),", put unit tests next to modules or in a dedicated folder, and keep E2E tests in a separate folder because they have different timing and requirements.\nA minimal example can be this:"]}),"\n",(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-json",children:'// package.json\n{\n  "scripts": {\n    "test": "vitest",\n    "test:e2e": "playwright test"\n  }\n}\n'})}),"\n",(0,r.jsx)(n.p,{children:"In your real project, script names and folders can change.\nWhat matters is separating fast core tests from slower tests that open a browser and run user flows."}),"\n",(0,r.jsxs)(n.p,{children:[(0,r.jsx)(n.strong,{children:"Rule:"})," Vitest for unit tests of pure functions, Playwright for E2E of critical flows.\nThe pyramid: many unit tests, few E2E.\nTest what breaks often, not everything out of duty.\nWithout tests, you lose automatic feedback and must recheck everything by hand."]}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)("br",{}),"\n",(0,r.jsx)(n.hr,{}),"\n",(0,r.jsx)(n.h3,{id:"summary-browser-and-async-in-brief",children:"Summary (Browser and Async in Brief)"}),"\n",(0,r.jsxs)(n.table,{children:[(0,r.jsx)(n.thead,{children:(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.th,{children:"Concept"}),(0,r.jsx)(n.th,{children:"Key rule"}),(0,r.jsx)(n.th,{children:"Common trap"})]})}),(0,r.jsxs)(n.tbody,{children:[(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"<script defer>"})}),(0,r.jsx)(n.td,{children:"Default for your app's JS, in the head"}),(0,r.jsx)(n.td,{children:"Without defer, DOM does not exist yet"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"<script async>"})}),(0,r.jsx)(n.td,{children:"Only for scripts independent from the DOM"}),(0,r.jsx)(n.td,{children:"Order between scripts not guaranteed"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:'type="module"'})}),(0,r.jsx)(n.td,{children:"Own scope, automatic defer"}),(0,r.jsxs)(n.td,{children:["CORS when opening files with ",(0,r.jsx)(n.code,{children:"file://"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"getElementById"})}),(0,r.jsx)(n.td,{children:"Direct for id, clear intention"}),(0,r.jsxs)(n.td,{children:["Using ",(0,r.jsx)(n.code,{children:'querySelector("#id")'})," by habit"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"querySelectorAll"})}),(0,r.jsx)(n.td,{children:"Static NodeList, predictable"}),(0,r.jsxs)(n.td,{children:["Using live ",(0,r.jsx)(n.code,{children:"getElements*"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Properties vs methods"}),(0,r.jsxs)(n.td,{children:["Properties without ",(0,r.jsx)(n.code,{children:"()"}),", methods with ",(0,r.jsx)(n.code,{children:"()"})]}),(0,r.jsxs)(n.td,{children:["Calling ",(0,r.jsx)(n.code,{children:'textContent("Hello")'})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"textContent"})}),(0,r.jsx)(n.td,{children:"Plain text, safe"}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"innerHTML"})," with user input without escaping or sanitization"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"classList"})}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"add"}),", ",(0,r.jsx)(n.code,{children:"remove"}),", ",(0,r.jsx)(n.code,{children:"toggle"}),", ",(0,r.jsx)(n.code,{children:"contains"})]}),(0,r.jsxs)(n.td,{children:["Modifying ",(0,r.jsx)(n.code,{children:".style"})," directly"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"data-*"})," + ",(0,r.jsx)(n.code,{children:"dataset"})]}),(0,r.jsx)(n.td,{children:"Connection between HTML and JavaScript"}),(0,r.jsx)(n.td,{children:"Forgetting they are always strings"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"addEventListener"})}),(0,r.jsxs)(n.td,{children:["Always this, never ",(0,r.jsx)(n.code,{children:"onclick"})]}),(0,r.jsxs)(n.td,{children:["Passing ",(0,r.jsx)(n.code,{children:"fn()"})," instead of ",(0,r.jsx)(n.code,{children:"fn"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"removeEventListener"})}),(0,r.jsx)(n.td,{children:"Needs the same function reference"}),(0,r.jsx)(n.td,{children:"Using two identical anonymous functions"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"e.target"})," vs ",(0,r.jsx)(n.code,{children:"e.currentTarget"})]}),(0,r.jsx)(n.td,{children:"target = origin, currentTarget = listener"}),(0,r.jsx)(n.td,{children:"Confusing them in delegation"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Pointer Events"}),(0,r.jsx)(n.td,{children:"Mouse, touch, and pen with the same events"}),(0,r.jsxs)(n.td,{children:["Forgetting ",(0,r.jsx)(n.code,{children:"pointerleave"})," or ",(0,r.jsx)(n.code,{children:"pointercancel"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Event delegation"}),(0,r.jsxs)(n.td,{children:["One listener on the parent, ",(0,r.jsx)(n.code,{children:"closest()"})," to filter"]}),(0,r.jsx)(n.td,{children:"One listener for every element"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"<dialog>"})," + ",(0,r.jsx)(n.code,{children:"showModal()"})]}),(0,r.jsx)(n.td,{children:"Modal with automatic focus trap and backdrop"}),(0,r.jsx)(n.td,{children:"Ignoring it and building modals manually"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Promise"}),(0,r.jsx)(n.td,{children:"pending, fulfilled, rejected"}),(0,r.jsxs)(n.td,{children:["Using Promise chains without ",(0,r.jsx)(n.code,{children:".catch()"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"fetch"})}),(0,r.jsxs)(n.td,{children:["Not rejected on 4xx/5xx, check ",(0,r.jsx)(n.code,{children:"res.ok"})]}),(0,r.jsxs)(n.td,{children:["Believing ",(0,r.jsx)(n.code,{children:".catch()"})," catches HTTP errors"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"fetch"})," body"]}),(0,r.jsxs)(n.td,{children:["JSON as string, ",(0,r.jsx)(n.code,{children:"FormData"})," left to the browser"]}),(0,r.jsxs)(n.td,{children:["Setting JSON headers even with ",(0,r.jsx)(n.code,{children:"FormData"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"async"}),"/",(0,r.jsx)(n.code,{children:"await"})]}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"try"}),"/",(0,r.jsx)(n.code,{children:"catch"})," for errors"]}),(0,r.jsxs)(n.td,{children:["Using ",(0,r.jsx)(n.code,{children:"await"})," where it is not allowed"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"Promise.all"})}),(0,r.jsx)(n.td,{children:"Independent operations in parallel"}),(0,r.jsxs)(n.td,{children:["Sequential ",(0,r.jsx)(n.code,{children:"await"})," when you could parallelize"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"localStorage"})}),(0,r.jsx)(n.td,{children:"Strings, about 5 MB per origin, synchronous"}),(0,r.jsxs)(n.td,{children:["Saving objects without ",(0,r.jsx)(n.code,{children:"JSON.stringify"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"IndexedDB"})}),(0,r.jsx)(n.td,{children:"Large data, asynchronous, variable quota"}),(0,r.jsx)(n.td,{children:"Using the native API directly when a wrapper is enough"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Service Worker"}),(0,r.jsx)(n.td,{children:"Offline, controlled cache, managed updates"}),(0,r.jsx)(n.td,{children:"Adding it when it is not needed"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Web Worker"}),(0,r.jsx)(n.td,{children:"Heavy calculations in separate thread"}),(0,r.jsx)(n.td,{children:"Using it where it is not really needed"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Event Loop"}),(0,r.jsx)(n.td,{children:"Microtasks (Promise) before Tasks (setTimeout)"}),(0,r.jsx)(n.td,{children:"Blocking the Call Stack with heavy synchronous code"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"FormData"})}),(0,r.jsxs)(n.td,{children:["Collects data from ",(0,r.jsx)(n.code,{children:"<form>"})," via ",(0,r.jsx)(n.code,{children:"name"})]}),(0,r.jsxs)(n.td,{children:["Fields without ",(0,r.jsx)(n.code,{children:"name"})," are not included"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"URL"})," / ",(0,r.jsx)(n.code,{children:"URLSearchParams"})]}),(0,r.jsx)(n.td,{children:"Build URLs safely"}),(0,r.jsx)(n.td,{children:"Concatenating strings by hand"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"AbortController"})}),(0,r.jsxs)(n.td,{children:["Cancel ",(0,r.jsx)(n.code,{children:"fetch"})," from the browser"]}),(0,r.jsx)(n.td,{children:"Thinking it always stops the server too"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Error classes"}),(0,r.jsxs)(n.td,{children:["Distinguish error types with ",(0,r.jsx)(n.code,{children:"instanceof"})]}),(0,r.jsx)(n.td,{children:"Comparing strings in messages"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"navigator.clipboard"})}),(0,r.jsx)(n.td,{children:"Copy text via Promise, requires user click"}),(0,r.jsx)(n.td,{children:"Calling it outside an event handler"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Regex"}),(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:".test()"}),", ",(0,r.jsx)(n.code,{children:".match()"}),", ",(0,r.jsx)(n.code,{children:".replace()"})," with text patterns"]}),(0,r.jsx)(n.td,{children:"Parsing HTML or JSON with regex"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsxs)(n.td,{children:[(0,r.jsx)(n.code,{children:"replaceAll"})," with regex"]}),(0,r.jsxs)(n.td,{children:["Needs a global regex with ",(0,r.jsx)(n.code,{children:"g"})," flag"]}),(0,r.jsxs)(n.td,{children:["Using a regex without ",(0,r.jsx)(n.code,{children:"g"})]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Debounce"}),(0,r.jsx)(n.td,{children:"Waits for the end (live search)"}),(0,r.jsx)(n.td,{children:"Confusing it with throttle"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Throttle"}),(0,r.jsx)(n.td,{children:"Limits frequency (scroll)"}),(0,r.jsx)(n.td,{children:"Using it where debounce is needed"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:(0,r.jsx)(n.code,{children:"escapeHtml"})}),(0,r.jsxs)(n.td,{children:["Escape text before ",(0,r.jsx)(n.code,{children:"innerHTML"})]}),(0,r.jsx)(n.td,{children:"Confusing it with complete HTML sanitization"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Configuration"}),(0,r.jsx)(n.td,{children:"Data describes, code executes"}),(0,r.jsx)(n.td,{children:"Hardcoding every variant"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Immutability"}),(0,r.jsx)(n.td,{children:"Create copies, do not mutate"}),(0,r.jsxs)(n.td,{children:["Using ",(0,r.jsx)(n.code,{children:"push"}),", ",(0,r.jsx)(n.code,{children:"splice"}),", ",(0,r.jsx)(n.code,{children:"sort"})," on shared state"]})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"Unit test"}),(0,r.jsx)(n.td,{children:"Vitest, pure functions, milliseconds"}),(0,r.jsx)(n.td,{children:"Losing automatic feedback on calculations"})]}),(0,r.jsxs)(n.tr,{children:[(0,r.jsx)(n.td,{children:"E2E test"}),(0,r.jsx)(n.td,{children:"Playwright, critical flows, real browser"}),(0,r.jsx)(n.td,{children:"Using it for every detail, too slow"})]})]})]})]})}function u(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(h,{...e})}):h(e)}},8453:(e,n,t)=>{t.d(n,{R:()=>i,x:()=>o});var s=t(6540);const r={},a=s.createContext(r);function i(e){const n=s.useContext(a);return s.useMemo(function(){return"function"==typeof e?e(n):{...n,...e}},[n,e])}function o(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:i(e.components),s.createElement(a.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.