Module 3

Browser & DOM

Window â€ĸ Document â€ĸ DOM Tree â€ĸ Selectors â€ĸ Events â€ĸ AJAX

🌐 JavaScript in the Browser

When JavaScript runs in a browser, it gets access to powerful APIs for manipulating the web page. The DOM (Document Object Model) is the bridge between your JavaScript code and the HTML on screen.

In Module 2, you mastered modern JavaScript syntax — spread, destructuring, Promises, and async/await. Now it's time to use those skills in the browser. This module covers everything from the global window object to selecting and creating DOM elements, handling user events, and fetching data from servers — the complete toolkit for building interactive web pages.

đŸĒŸ Window 📄 Document đŸŒŗ DOM APIs đŸ“Ļ Script Loading đŸŽ¯ Events 📡 AJAX

đŸŽ¯ By the end of this module, you will:

  • Understand the window and document objects
  • Navigate the DOM tree and its inheritance hierarchy
  • Select, create, modify, traverse, and remove DOM elements
  • Handle events: types, flow (capture/bubble), delegation
  • Choose the right script loading strategy (defer, async)
  • Fetch data with XMLHttpRequest and fetch()
  • Understand the evolution from Callback Hell to Promise.all
🚀

Want to practice? Try our Web Playground!

Write HTML, CSS & JavaScript with live preview — no signup required.

Launch Playground →

đŸĒŸ The window Global

Each browser tab has a top-level global object: window. It exposes:

🌍 Environment
location navigator history screen
âąī¸ Timing
setTimeout setInterval requestAnimationFrame
📄 DOM Root
document
đŸ’Ŧ Dialogs / Console
alert prompt confirm console

💡 Tip: You can omit window. for global functions: alert("Hi") equals window.alert("Hi")

console.log("Current URL:", window.location.href);
console.log("User Agent:", window.navigator.userAgent);
console.log("Screen size:", window.screen.width, "x", window.screen.height);

// Timing example
window.setTimeout(() => console.log("Timer fired after 500ms"), 500);

// Another way (window. is optional)
setTimeout(() => console.log("Same timer syntax"), 600);

📄 The document Object

document represents the loaded HTML page. It is your entry point for reading and changing anything on screen — from the page title to individual elements. Key properties:

📍 Element Shortcuts
document.documentElement → <html>
document.head → <head>
document.body → <body>
â„šī¸ Info Properties
document.title (read/write)
document.URL
document.referrer
🔄 Ready State
document.readyState
loading interactive complete

💡 Try it now: Open your browser DevTools (F12) and type document.title = "Hello!" — watch the tab title change in real-time!

// Change the page title (watch the browser tab!)
document.title = "My Custom Title!";
console.log("Title:", document.title);

// Access key elements
console.log("URL:", document.URL);
console.log("Ready state:", document.readyState);
console.log("Character set:", document.characterSet);

// Direct element shortcuts
const html = document.documentElement;  // <html>
const head = document.head;             // <head>
const body = document.body;             // <body>

console.log("HTML lang:", html.lang);
console.log("Body children:", body.children.length);

đŸŒŗ The DOM Tree

The DOM (Document Object Model) is a tree of nodes. The browser parses your HTML and builds this tree. JavaScript can read and modify it in real-time.

HTML → DOM Tree

📄 HTML Source
<!-- HTML Source -->
<html>
  <head>
    <title>My Page</title>
  </head>
  <body>
    <h1>Hello</h1>
    <p class="intro">Welcome!</p>
  </body>
</html>
→
đŸŒŗ DOM Tree
document
html
head
title"My Page"
body
h1"Hello"
p.intro"Welcome!"

Node Types

đŸ“Ļ Element nodes 📝 Text nodes đŸ’Ŧ Comment nodes 📎 Attribute nodes

Key Relationships

âŦ†ī¸ Parent Access
node.parentNode
Every node may have a parent
âŦ‡ī¸ Children Access
children (elements only)
childNodes (all node types)
â†”ī¸ Siblings
nextElementSibling
previousElementSibling

đŸ›ī¸ DOM Inheritance Hierarchy

Accurate core DOM type relationships (simplified). Click nodes to expand/collapse branches.

Understanding DOM inheritance explains why methods work the way they do. Every <div>, <button>, or <input> is an instance of a specific class (like HTMLDivElement) that inherits from HTMLElement → Element → Node. That's why you can call .addEventListener() on any element — it's inherited from EventTarget, the root of the chain.

  • â–ŧ Node
    • 📄 Document
    • 📄 DocumentType
    • 📄 DocumentFragment
    • â–ŧ Element
      • â–ŧ HTMLElement
        • 📄 HTMLDivElement
        • 📄 HTMLButtonElement
        • 📄 HTMLInputElement
        • 📄 HTMLSpanElement
        • ⋯ and many more
      • â–ļ SVGElement
        • 📄 SVGCircleElement
        • 📄 SVGPathElement
        • ⋯ more
    • â–ŧ CharacterData
      • 📄 Text
      • 📄 Comment
      • 📄 CDATASection
    • 📄 ProcessingInstruction
📎 Attr Not a Node (since DOM4)

💡 Practical takeaway: You don't need to memorize this tree. Just know that specific elements (like HTMLInputElement) inherit everything from their ancestors — that's why .value exists on inputs but not divs, while .textContent and .addEventListener() work on everything.

đŸŽ¯ Selecting Elements

Before you can read, modify, or remove an element, you need to find it in the DOM. JavaScript provides several methods — from fast ID lookups to powerful CSS-selector-based queries. Choose the right one depending on what you need:

document.getElementById(id) Single element by ID ⚡ Fastest
document.querySelector(css) First match (any CSS selector) 🎨 Flexible
document.querySelectorAll(css) All matches → static NodeList 📋 Static
getElementsByClassName(name) By class → live HTMLCollection 🔄 Live
getElementsByTagName(tag) By tag → live HTMLCollection 🔄 Live

Static vs Live Collections

📋 Static NodeList

Snapshot at query time. DOM changes don't affect it. Returned by querySelectorAll.

🔄 Live HTMLCollection

Auto-updates when DOM changes. Can cause issues in loops! Returned by getElementsBy*.

â–ļ 🔍 Deep Dive: Static vs Live — Why It Matters

📌 The Core Concept

Think of it like a photo vs a mirror. A static collection is a photo — it captures the state at the moment you took it. A live collection is a mirror — it always reflects the current state of the DOM.

1. Setup
Your page has 3 items with class .product
↓
2. Query
You select them with both methods → both return 3 results
↓
3. Mutation
JavaScript adds a 4th .product element to the DOM
↓
4. Result
Static still says 3 📸  |  Live now says 4 đŸĒž
// === SETUP: Page has 3 .product divs ===

// 📸 Static snapshot (photo)
const staticList = document.querySelectorAll('.product');

// đŸĒž Live mirror
const liveList = document.getElementsByClassName('product');

console.log(staticList.length); // 3
console.log(liveList.length);   // 3

// === MUTATION: Add a 4th product ===
const newProduct = document.createElement('div');
newProduct.className = 'product';
document.body.appendChild(newProduct);

// === CHECK AGAIN ===
console.log(staticList.length); // 3 — still! (photo doesn't change)
console.log(liveList.length);   // 4 — updated! (mirror reflects reality)

âš ī¸ The Dangerous Loop Bug

Live collections can cause infinite loops when you modify the DOM inside a loop, because the collection keeps updating:

// ❌ DANGEROUS — Infinite loop!
const items = document.getElementsByClassName('old');

for (let i = 0; i < items.length; i++) {
    items[i].className = 'new';
    // Problem: items.length SHRINKS as items lose 'old' class!
    // i=0 → changes first item → length drops from 3 to 2
    // i=1 → skips the SECOND item (now at index 0)!
    // Items get skipped, or loop runs forever if you ADD the class
}

// ✅ SAFE — Static doesn't change during loop
const items2 = document.querySelectorAll('.old');

items2.forEach(item => {
    item.className = 'new';  // Safe! items2 is frozen
});

// ✅ ALSO SAFE — Convert live to static array first
const items3 = [...document.getElementsByClassName('old')];

items3.forEach(item => {
    item.className = 'new';  // Safe! Array is a copy
});
📋 Static NodeList 🔄 Live HTMLCollection
Method querySelectorAll() getElementsBy*()
Auto-updates? ❌ No — frozen snapshot ✅ Yes — always current
forEach? ✅ Native support ❌ Must convert: [...list]
Loop-safe? ✅ Always safe âš ī¸ Risky if DOM mutates
Performance Slightly slower (builds snapshot) Faster (no copy needed)
Use when Iterating, modifying, storing Checking count, quick reads

🏆 Rule of thumb: Always use querySelectorAll() unless you specifically need live updates. It's safer, more powerful (any CSS selector), and supports forEach natively.

🚀 Direct access: document.body, document.head, document.documentElement — no selector needed!

// Modern selectors (recommended ✅)
const el = document.querySelector('.my-class');      // first match
const all = document.querySelectorAll('.my-class');   // static NodeList

// By ID (fastest)
const header = document.getElementById('header');

// CSS selectors work!
document.querySelector('nav > ul > li:first-child');
document.querySelectorAll('[data-active="true"]');
document.querySelector('#form input[type="email"]');

// Checking elements
el.matches('.active');     // does it match this selector?
el.closest('.container');  // nearest ancestor matching selector

// âš ī¸ Live vs Static
const live = document.getElementsByClassName('item');  // 🔄 Live
const snap = document.querySelectorAll('.item');        // 📋 Static

document.body.appendChild(document.createElement('div')).className = 'item';
console.log(live.length);  // increased! (live auto-updates)
console.log(snap.length);  // same! (snapshot doesn't change)

📝 Content & Attribute Manipulation

Once you've selected an element, you can change its content, attributes, styles, and classes. JavaScript offers multiple ways to update what the user sees — from safe text replacement to full HTML injection. Understanding the differences is critical for both functionality and security.

Text/Content APIs Comparison

textContent ✅ Safe ⚡ Fast

Raw text only. No layout cost. HTML is escaped as text.

innerText âš ī¸ Reflow

Respects CSS visibility. Triggers layout/reflow reads.

innerHTML âš ī¸ XSS Risk

Parses HTML. Never use with untrusted input!

🔒 Security: Sanitize external HTML (e.g. DOMPurify) or build nodes manually to prevent XSS attacks.

📎 Dataset API (data-* attributes)

HTML Attribute
data-user-id="123"
→
JavaScript Access
element.dataset.userId
READ el.dataset.userId
WRITE el.dataset.key = "val"
DELETE delete el.dataset.key

âš ī¸ Important: Use dataset, not data: el.dataset.ok ✅ vs el.data.ok ❌

const panel = document.querySelector("#panel");

// textContent - treats HTML as literal text (safe ✅)
panel.textContent = "<span>Shows as TEXT</span>";

// innerHTML - renders HTML (XSS risk âš ī¸)
panel.innerHTML = "<strong style='color:green'>Bold HTML!</strong>";

// Attributes
const input = document.querySelector("#email");
input.value = "user@example.com";
input.setAttribute("placeholder", "Enter email");

// Styles
el.style.color = 'red';
el.style.backgroundColor = 'blue';

// Better: toggle classes (let CSS handle styles)
el.classList.add('active');
el.classList.remove('hidden');
el.classList.toggle('open');
el.classList.contains('active');  // true/false

// Dataset API
panel.dataset.state = "active";       // creates data-state="active"
panel.dataset.userId = "12345";       // creates data-user-id="12345"
console.log(panel.dataset.state);     // "active"
delete panel.dataset.state;           // removes data-state

đŸ› ī¸ Creating & Inserting Elements

Dynamic web pages don't just display static HTML — they build new elements on the fly. Whether you're rendering a list of search results, adding a notification banner, or creating a modal dialog, you need to create DOM nodes in JavaScript and insert them at the right place in the tree.

CREATE document.createElement(tag)
APPEND parent.appendChild(node)
parent.append(...nodes)
PREPEND parent.prepend(node)
INSERT HTML insertAdjacentHTML(pos, html)

📍 insertAdjacentHTML positions

"beforebegin" → before element
<div>
"afterbegin" → first child
...content...
"beforeend" → last child
</div>
"afterend" → after element

🚀 DocumentFragment — Batch Insertions

Every DOM modification triggers a reflow/repaint (expensive!)

❌ Without Fragment

Adding 100 items = 100 reflows!

const parent = document.body;

for (let i = 0; i < 100; i++) {
  const item = document.createElement('div');
  item.textContent = 'Item ' + i;
  parent.appendChild(item);  // reflow each time!
}

✅ With Fragment

Adding 100 items = 1 reflow!

const parent = document.body;
const frag = document.createDocumentFragment();

for (let i = 0; i < 100; i++) {
  const item = document.createElement('div');
  item.textContent = 'Item ' + i;
  frag.appendChild(item);
}
parent.appendChild(frag);  // ONE reflow!
💾 Lives in memory only đŸ‘ģ Fragment disappears after insert 📊 Perfect for lists & tables
// 1. Create element
const div = document.createElement("div");
div.textContent = "Hello!";
div.className = "my-class";

// 2. Insert methods
container.prepend(div);              // First child
container.append(div);               // Last child
container.appendChild(div);          // Same as append

// 3. insertAdjacentHTML positions
container.insertAdjacentHTML("beforebegin", "<p>Before</p>");
container.insertAdjacentHTML("afterbegin", "<p>First inside</p>");
container.insertAdjacentHTML("beforeend", "<p>Last inside</p>");
container.insertAdjacentHTML("afterend", "<p>After</p>");

// 4. DocumentFragment for batch inserts
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
  const item = document.createElement("div");
  item.textContent = "Item " + i;
  fragment.appendChild(item);  // No reflow yet!
}
container.appendChild(fragment);  // ONE reflow!

// 5. Remove & Clone
div.remove();                        // Remove from DOM
const clone = div.cloneNode(true);   // Deep clone

🎮 Interactive Playground — Try It!

Choose Method:
Generated Code:
const newEl = document.createElement('div');
newEl.textContent = "New Item ⭐";
container.prepend(newEl);
Live Preview:
âŦ†ī¸ beforebegin zone (outside)
đŸ“Ļ CONTAINER (target element)
afterbegin / prepend
đŸ“Ļ Item 1 (original)
đŸ“Ļ Item 2 (original)
đŸ“Ļ Item 3 (original)
beforeend / append
âŦ‡ī¸ afterend zone (outside)
đŸ”ĩ Original items ✨ New items

🧭 Traversal & Relationships

Navigate the DOM tree using relationship properties:

PARENT
↓ children
firstElementChild
Child 1
↑ prev │ next ↓
children[1]
Child 2
↑ prev │ next ↓
lastElementChild
Child 3
↑ parentNode
CHILDREN (elements only)
element.children
CHILD NODES (all types)
element.childNodes
PARENT
node.parentNode
SIBLINGS
nextElementSibling
previousElementSibling

Element Properties

Returns only element nodes (tags). children, firstElementChild, nextElementSiblingâ€Ļ

Node Properties

Includes text, comments, etc. childNodes, firstChild, nextSiblingâ€Ļ

const container = document.querySelector("#articles");

// Children info
console.log("Element children:", container.children.length);
console.log("All child nodes:", container.childNodes.length);

// First and last
const first = container.firstElementChild;
const last = container.lastElementChild;
console.log("First:", first.textContent);
console.log("Last:", last.textContent);

// Sibling navigation
const second = first.nextElementSibling;
console.log("Second:", second.textContent);

// Go back up
console.log("Parent:", second.parentNode.tagName);

// Find nearest ancestor
const btn = document.querySelector('.delete-btn');
const card = btn.closest('.card-wrapper');  // nearest ancestor

📜 Script Loading Strategies

Where and how you load your <script> tags affects page performance:

🛑 <script> (default)

Blocks HTML parsing. Browser stops, downloads, runs, then continues.

  • ❌ Blocks page rendering
  • ❌ Slows down page load
  • ✅ Scripts execute in order

âŗ <script defer> ✅

Downloads in parallel, runs after parsing. Before DOMContentLoaded.

  • ✅ HTML parsing never blocked
  • ✅ Scripts execute in order
  • ✅ Full DOM is available

⚡ <script async>

Downloads in parallel, runs immediately. Order not guaranteed.

  • ✅ Downloads in parallel
  • âš ī¸ Order NOT guaranteed
  • âš ī¸ DOM may be incomplete

đŸŽŦ Timeline Visualization

HTML
Script
HTML Parsing Blocked Download Execute

🛑 Blocking: HTML parsing stops at the script tag. Browser downloads and executes, then resumes. DOMContentLoaded waits for the script.

📍 When do browser events fire?

DOMContentLoaded

HTML fully parsed, DOM ready. Images/stylesheets may still be loading.

window.load

Everything loaded (images, styles, iframes). Full page is ready.

đŸ“Ļ Note: type="module" scripts behave exactly like defer by default!

<!-- In <head> with defer — RECOMMENDED ✅ -->
<head>
    <script src="app.js" defer></script>
</head>

<!-- async — for independent scripts -->
<script src="analytics.js" async></script>

<!-- DOMContentLoaded — wait for DOM to be ready -->
<script>
document.addEventListener('DOMContentLoaded', () => {
    const app = document.getElementById('app');
});
</script>

<!-- module scripts = defer by default -->
<script type="module" src="app.mjs"></script>

⚡ Events: How JavaScript Listens to the Browser

Events are signals the browser sends when something happens — a click, key press, page load, etc. Your code can "listen" and respond.

đŸŽ¯ What is an Event?

An event is the browser saying: "Hey, something just happened!"

👆
User Action
→
📡
Browser fires Event
→
⚡
Your Code Runs

📝 How to Listen for Events

❌ Inline (Don't Do This)

<button onclick="alert('Hi')">
  • Mixes HTML with JS
  • Only ONE handler per event
  • Hard to debug

✅ addEventListener

el.addEventListener("click", fn)
  • Clean separation
  • Multiple handlers
  • Can remove listeners

💡 Key Syntax: element.addEventListener("eventType", callbackFunction)

📋 Common Event Types

đŸ–ąī¸ Mouse Events

clickdblclickmouseentermouseleavemousemovecontextmenu

âŒ¨ī¸ Keyboard Events

keydownkeyupkeypress (deprecated)

📝 Form Events

submitchangeinputfocusblur

🌐 Page Events

DOMContentLoadedloadscrollresize

đŸ“Ļ The Event Object

Every handler receives an event object with useful info:

e.targetElement that triggered the event
e.currentTargetElement the listener is on
e.typeEvent type ("click", etc.)
e.preventDefault()Stop default behavior
e.stopPropagation()Stop bubbling up
e.keyWhich key was pressed
e.clientX / clientYMouse position (viewport)
button.addEventListener('click', (e) => {
  console.log(e.type);       // "click"
  console.log(e.target);     // the <button> element
  console.log(e.clientX);    // mouse X position
  console.log(e.timeStamp);  // when event occurred (ms)
  e.preventDefault();        // stop default action
  e.stopPropagation();       // stop event bubbling
});

🌊 Event Flow: Capturing & Bubbling

When you click an element, the event travels in 3 phases:

1CAPTURE↓ Down

From window down to target

2TARGET●

At the clicked element

3BUBBLE↑ Up

From target up to window

// BUBBLING (default) - Events go UP ↑
outer.addEventListener('click', () => console.log('OUTER'));
middle.addEventListener('click', () => console.log('MIDDLE'));
inner.addEventListener('click', () => console.log('INNER'));
// Click inner → INNER, MIDDLE, OUTER

// CAPTURING - Events go DOWN ↓ (third param = true)
outer.addEventListener('click', () => console.log('OUTER'), true);
// Click inner → OUTER, MIDDLE, INNER

// Stop propagation
inner.addEventListener('click', (e) => {
  e.stopPropagation();  // parents won't receive it
  console.log('INNER - STOPPED');
});

🤔 Why would you use Capture Phase?

Capture lets you intercept events before they reach the target. Think of it like a security guard checking visitors before they enter a building:

📋 Use Case 1: Validation

Parent can validate or cancel events before children handle them

📋 Use Case 2: Logging

Track all clicks from the top before they reach their targets

📋 Use Case 3: Focus Trap

Modal dialogs can capture focus events to keep focus inside

💡 99% of the time you'll use bubbling (default). Capture is for special cases where you need to act first.

🔴 Click the Inner Box — Watch the Flow!

OUTER (grandparent)
MIDDLE (parent)
INNER (target)
↑ Bubbling: stopPropagation() on INNER prevents MIDDLE and OUTER from receiving the event.

đŸŽĒ Event Delegation

Instead of adding listeners to many children, add ONE listener to the parent!

❌ Bad: Many Listeners

// 100 buttons = 100 listeners 😰
buttons.forEach(btn => {
  btn.addEventListener("click", handle);
});

✅ Good: Delegation

// 1 listener handles all! 🎉
container.addEventListener("click", e => {
  if (e.target.matches("button")) {
    handle(e.target);
  }
});
✅ Less memory✅ Works on dynamic elements✅ Easier cleanup
// Practical: Todo list with delegation
const list = document.querySelector('.todo-list');

// ONE listener handles ALL children
list.addEventListener('click', (e) => {
  if (e.target.matches('.delete-btn')) {
    e.target.closest('.todo-item').remove();
  } else if (e.target.tagName === 'LI') {
    e.target.classList.toggle('selected');
  }
});

// New items work automatically!
function addItem(text) {
  const li = document.createElement('li');
  li.innerHTML = `${text} <button class="delete-btn">×</button>`;
  list.appendChild(li);
  // No new listener needed - parent already handles it!
}

// Key methods: e.target, e.target.matches(), e.target.closest()

// Remove & once
btn.removeEventListener('click', handler);
btn.addEventListener('click', fn, { once: true }); // fires once

📡 AJAX: From XHR to Modern Fetch

AJAX lets you request data from a server without reloading the page. Let's trace the evolution from XHR to modern fetch().

📡 XMLHttpRequest (XHR) — 5 Steps

1Createnew XMLHttpRequest()
→
2Configurexhr.open(method, url)
→
3Optionsxhr.responseType
→
4Listenersxhr.addEventListener()
→
5Sendxhr.send()
load Success error Network fail progress Downloading loadend Finished timeout Too slow abort Cancelled

📋 Setting Request Headers

Use setRequestHeader(name, value) after open() but before send()

JSON Data
xhr.setRequestHeader("Content-Type", "application/json")
Form Data
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
Authorization
xhr.setRequestHeader("Authorization", "Bearer token123")
Custom Header
xhr.setRequestHeader("X-Custom-Header", "value")

📤 Sending Data with send()

📭 No Data (GET)
xhr.send() or xhr.send(null)
đŸ“Ļ JSON (POST/PUT)
xhr.send(JSON.stringify(data))
📝 FormData
xhr.send(new FormData(form))
🔗 URL Encoded
xhr.send("name=John&age=25")
// Full Async XHR Example
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://jsonplaceholder.typicode.com/posts/1", true);
xhr.responseType = "json";

xhr.addEventListener("load", () => {
  if (xhr.status === 200) {
    console.log("✅ Success:", xhr.response.title);
  } else {
    console.log("❌ Error:", xhr.status);
  }
});
xhr.addEventListener("error", () => console.log("❌ Network error!"));
xhr.send();

✅ Async (Recommended)

xhr.open("GET", url, true)
  • Non-blocking
  • UI stays responsive
  • Uses event listeners

⛔ Sync (Avoid!)

xhr.open("GET", url, false)
  • Blocks browser
  • UI freezes
  • Deprecated in main thread

✨ Modern fetch() API

// Basic GET request
const response = await fetch('https://api.example.com/users');
const users = await response.json();

// With error handling
async function fetchUsers() {
    try {
        const res = await fetch('/api/users');
        if (!res.ok) throw new Error(`HTTP ${res.status}`);
        return await res.json();
    } catch (err) {
        console.error('Fetch failed:', err.message);
    }
}

// POST request (sending data)
await fetch('/api/users', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name: 'Mehdi', role: 'teacher' })
});

// Other methods
await fetch('/api/users/1', { method: 'PUT', headers: {...}, body: ... });
await fetch('/api/users/1', { method: 'DELETE' });

// Practical: load data and update DOM
async function loadPosts() {
    const container = document.getElementById('posts');
    container.innerHTML = '<p>Loading...</p>';
    try {
        const res = await fetch('/api/posts');
        const posts = await res.json();
        container.innerHTML = posts
            .map(p => `<article><h3>${p.title}</h3></article>`)
            .join('');
    } catch(e) {
        container.innerHTML = '<p class="error">Failed to load</p>';
    }
}

Important: fetch() only rejects on network errors, NOT on HTTP errors (404, 500). Always check response.ok.

🔄 From Callback Hell to Modern Fetch

Compare how we fetch 10 numbers and sum them — from the worst pattern to the best. Click the buttons below to run each approach live and see the timing difference!

Sum of 10 numbers: — = — ⏱ —
👆 Click a button to compare approaches...
đŸšĢ Never Use — Deprecated, blocks the entire browser!
âš ī¸
XHR Sync blocks the entire UI! The browser freezes completely — user cannot scroll, click, or interact with anything until the request completes. This is deprecated and should never be used in production!
// đŸšĢ XHR SYNCHRONOUS: NEVER USE THIS!
// The third parameter "false" makes it synchronous
// Using DummyJSON API — fetching product prices as random numbers

const getAPI = () =>
  `https://dummyjson.com/products/${Math.ceil(Math.random()*100)}`;

const xhr1 = new XMLHttpRequest();
xhr1.open("GET", getAPI(), false); xhr1.send();
const n1 = Math.round(JSON.parse(xhr1.responseText).price);

const xhr2 = new XMLHttpRequest();
xhr2.open("GET", getAPI(), false); xhr2.send();
const n2 = Math.round(JSON.parse(xhr2.responseText).price);

// ... (n3 through n9 — same pattern) ...

const xhr10 = new XMLHttpRequest();
xhr10.open("GET", getAPI(), false); xhr10.send();
const n10 = Math.round(JSON.parse(xhr10.responseText).price);

const sum = n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10;
console.log("Sum:", sum);
// đŸšĢ UI was frozen the whole time!
❌ Avoid — Deep nesting, repeated error handling, hard to maintain
// 😰 CALLBACK HELL: 10 nested callbacks!
// Using DummyJSON API — fetching product prices as random numbers

const getAPI = () =>
  `https://dummyjson.com/products/${Math.ceil(Math.random()*100)}`;

const getRandomAsync = (onSuccess, onFail) => {
  const xhr = new XMLHttpRequest();
  xhr.open("GET", getAPI(), true);
  xhr.onload = () => xhr.status === 200
    ? onSuccess(Math.round(JSON.parse(xhr.response).price))
    : onFail(xhr.status);
  xhr.onerror = () => onFail("Network error");
  xhr.send();
};

// The pyramid of doom 😱
getRandomAsync(n1 => {
  getRandomAsync(n2 => {
    getRandomAsync(n3 => {
      getRandomAsync(n4 => {
        getRandomAsync(n5 => {
          getRandomAsync(n6 => {
            getRandomAsync(n7 => {
              getRandomAsync(n8 => {
                getRandomAsync(n9 => {
                  getRandomAsync(n10 => {
                    const sum = n1+n2+n3+n4+n5+n6+n7+n8+n9+n10;
                    console.log("Sum:", sum);
                  }, e => console.error(e));
                }, e => console.error(e));
              }, e => console.error(e));
            }, e => console.error(e));
          }, e => console.error(e));
        }, e => console.error(e));
      }, e => console.error(e));
    }, e => console.error(e));
  }, e => console.error(e));
}, e => console.error(e));
// âąī¸ Total time: ~2000ms (sequential!)
✓ Better — Flat chain, single error handler, but still sequential
// 🔗 PROMISE CHAINS: Flat but still sequential!
// Using DummyJSON API — fetching product prices as random numbers

const getAPI = () =>
  `https://dummyjson.com/products/${Math.ceil(Math.random()*100)}`;

const getRandomFromServer = () => {
  return fetch(getAPI())
    .then(res => {
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      return res.json();
    })
    .then(data => Math.round(data.price));
};

let n1, n2, n3, n4, n5, n6, n7, n8, n9, n10;

getRandomFromServer()
  .then(n => { n1 = n; return getRandomFromServer(); })
  .then(n => { n2 = n; return getRandomFromServer(); })
  .then(n => { n3 = n; return getRandomFromServer(); })
  .then(n => { n4 = n; return getRandomFromServer(); })
  .then(n => { n5 = n; return getRandomFromServer(); })
  .then(n => { n6 = n; return getRandomFromServer(); })
  .then(n => { n7 = n; return getRandomFromServer(); })
  .then(n => { n8 = n; return getRandomFromServer(); })
  .then(n => { n9 = n; return getRandomFromServer(); })
  .then(n => {
    n10 = n;
    const sum = n1+n2+n3+n4+n5+n6+n7+n8+n9+n10;
    console.log("Sum:", sum);
  })
  .catch(err => console.error(err));
// âąī¸ Total time: ~2000ms (still sequential!)
✅ Recommended — Clean, reads like sync code, easy try/catch — but still sequential!
// ✨ ASYNC/AWAIT: Clean but SEQUENTIAL (slow for 10 requests!)
// Using DummyJSON API — fetching product prices as random numbers

const getAPI = () =>
  `https://dummyjson.com/products/${Math.ceil(Math.random()*100)}`;

const getRandomFromServer = async () => {
  const response = await fetch(getAPI());
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const data = await response.json();
  return Math.round(data.price);
};

(async () => {
  // âš ī¸ Each await WAITS for the previous one!
  const n1 = await getRandomFromServer();
  const n2 = await getRandomFromServer();
  const n3 = await getRandomFromServer();
  const n4 = await getRandomFromServer();
  const n5 = await getRandomFromServer();
  const n6 = await getRandomFromServer();
  const n7 = await getRandomFromServer();
  const n8 = await getRandomFromServer();
  const n9 = await getRandomFromServer();
  const n10 = await getRandomFromServer();

  const sum = n1+n2+n3+n4+n5+n6+n7+n8+n9+n10;
  console.log("Sum:", sum);
})();
// âąī¸ Total time: ~2000ms (10 sequential requests)
🏆 Best — Parallel execution — 10× faster for independent requests!
// ⚡ PROMISE.ALL: All 10 requests run at the SAME TIME!
// Using DummyJSON API — fetching product prices as random numbers

const getAPI = () =>
  `https://dummyjson.com/products/${Math.ceil(Math.random()*100)}`;

const getRandomFromServer = async () => {
  const response = await fetch(getAPI());
  if (!response.ok) throw new Error(`HTTP ${response.status}`);
  const data = await response.json();
  return Math.round(data.price);
};

(async () => {
  // 🚀 All 10 requests start simultaneously!
  const [n1, n2, n3, n4, n5, n6, n7, n8, n9, n10] = await Promise.all([
    getRandomFromServer(),
    getRandomFromServer(),
    getRandomFromServer(),
    getRandomFromServer(),
    getRandomFromServer(),
    getRandomFromServer(),
    getRandomFromServer(),
    getRandomFromServer(),
    getRandomFromServer(),
    getRandomFromServer()
  ]);

  const sum = n1+n2+n3+n4+n5+n6+n7+n8+n9+n10;
  console.log("Sum:", sum);
})();
// âąī¸ Total time: ~200ms (all 10 in parallel!)
// 🏆 That's 10× FASTER than sequential!

Promise Combinators Summary

🔗
Sequential
When each request depends on the previous
⚡
Promise.all
Independent requests (fastest!)
đŸ›Ąī¸
Promise.allSettled
Results even if some fail
🏃
Promise.race
Only the fastest response

📝 Summary

This module covered the entire browser-side JavaScript toolkit. You now know how the window and document objects work, how to navigate and manipulate the DOM tree, how to select, create, and insert elements efficiently, how events flow through the page (capture → target → bubble), and how to fetch data from servers with XHR and the modern Fetch API. These are the skills that turn static HTML into living, interactive web applications. In the next module, you'll leave the browser and enter the world of Node.js — JavaScript on the server.

đŸĒŸ Window

Global object: environment, timing, dialogs. window. is optional.

📄 Document

Loaded HTML page. Shortcuts: .body, .head, .title.

đŸŒŗ DOM Tree

Tree of nodes. Element, text, comment, attribute types.

đŸ›ī¸ Inheritance

Node → Element → HTMLElement → specific elements.

đŸŽ¯ Selectors

querySelector / querySelectorAll. Static vs Live.

📝 Manipulation

textContent (safe), innerHTML (XSS risk), Dataset API.

đŸ› ī¸ Creation

createElement, append, insertAdjacentHTML, Fragment.

🧭 Traversal

children, parentNode, nextElementSibling, closest.

📜 Scripts

defer for most. async for independent. module = defer.

⚡ Events

Capture → Target → Bubble. addEventListener, delegation.

📡 XHR

5-step lifecycle. Async recommended. Legacy but useful.

🌍 Fetch

Modern AJAX. async/await + Promise.all for parallel.

4
Next Module 4 / 7

Module 4: Node.js Fundamentals

Node.js runtime, modules, built-in APIs (fs, path, os), and creating your first HTTP server.

Node.js require / import fs path HTTP server npm
→