Master JavaScript Web APIs: how browser APIs enable async programming, setTimeout, setInterval, fetch, DOM events, Geolocation, Web Workers, and their role in the event loop.
Introduction
Web APIs are interfaces provided by the browser (or Node.js) that allow JavaScript to interact with the outside world — making network requests, accessing the DOM, setting timers, reading location, and much more. Critically, Web APIs run outside the JavaScript engine, often on separate threads, enabling non-blocking asynchronous behavior.
Without Web APIs, JavaScript — being single-threaded — would have to freeze the entire page every time it made a network request or waited for a timer.
Key insight: The JavaScript engine (V8, SpiderMonkey) does NOT handle timers, network requests, or DOM events directly. These are all delegated to Web APIs.
JavaScript Engine vs Web APIs
┌──────────────────────────────────────────────────────────────────┐
│ JAVASCRIPT RUNTIME │
│ │
│ ┌─────────────────────────┐ ┌──────────────────────────────┐ │
│ │ JAVASCRIPT ENGINE │ │ WEB APIs │ │
│ │ (Handles JS code) │ │ (Provided by Browser) │ │
│ │ │ │ │ │
│ │ • Parsing JS │ │ Timers: │ │
│ │ • Compiling to machine │ │ • setTimeout() │ │
│ │ code (JIT) │ │ • setInterval() │ │
│ │ • Call Stack │ │ • clearTimeout() │ │
│ │ • Memory Heap │ │ │ │
│ │ • Garbage Collection │ │ Network: │ │
│ │ │ │ • fetch() │ │
│ │ Does NOT do: │ │ • XMLHttpRequest │ │
│ │ ❌ Timers │ │ • WebSocket │ │
│ │ ❌ Network requests │ │ │ │
│ │ ❌ DOM manipulation │ │ DOM: │ │
│ │ ❌ File I/O │ │ • addEventListener() │ │
│ │ ❌ Location │ │ • querySelector() │ │
│ │ │ │ • document.* │ │
│ └─────────────────────────┘ │ │ │
│ │ Location & Device: │ │
│ │ • navigator.geolocation │ │
│ │ • DeviceOrientation │ │
│ │ • Battery API │ │
│ │ │ │
│ │ Storage: │ │
│ │ • localStorage │ │
│ │ • IndexedDB │ │
│ │ • sessionStorage │ │
│ │ │ │
│ │ Workers & Rendering: │ │
│ │ • Web Workers │ │
│ │ • requestAnimationFrame │ │
│ │ • Canvas API │ │
│ │ • WebGL │ │
│ └──────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────┘
How Web APIs Enable Asynchronous Behavior
┌─────────────────────────────────────────────────────────────────┐
│ WEB API ASYNC FLOW │
│ │
│ STEP 1: JS calls a Web API function │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ setTimeout(callback, 1000) │ │
│ │ fetch("https://api.example.com") │ │
│ │ navigator.geolocation.getCurrentPosition(cb) │ │
│ └────────────────────────┬────────────────────────────┘ │
│ │ Hands off task │
│ ▼ │
│ STEP 2: Web API handles task (outside JS engine) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Browser/OS runs timer, network request, etc. │ │
│ │ on a separate thread │ │
│ │ JavaScript continues executing other code │ │
│ └────────────────────────┬────────────────────────────┘ │
│ │ Task completes │
│ ▼ │
│ STEP 3: Callback placed in queue │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ CALLBACK QUEUE: [callback] │ │
│ │ (or MICROTASK QUEUE for Promises) │ │
│ └────────────────────────┬────────────────────────────┘ │
│ │ Event loop moves to stack │
│ ▼ │
│ STEP 4: Event loop pushes callback to call stack │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ (Only when call stack is empty) │ │
│ │ Callback executes! │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Timer APIs: setTimeout & setInterval
setTimeout — Execute Once After Delay
console.log("Before timer");
// setTimeout hands the callback to the Web API (Timer)
const timerId = setTimeout(() => {
console.log("Timer fired after 2 seconds!");
}, 2000);
console.log("After timer registration (runs immediately)");
// Cancel if needed
// clearTimeout(timerId);
Before timer
After timer registration (runs immediately)
Timer fired after 2 seconds! ← appears 2 seconds later
setInterval — Execute Repeatedly
let count = 0;
const startTime = Date.now();
const intervalId = setInterval(() => {
count++;
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
console.log(`Tick ${count} at ${elapsed}s`);
if (count >= 5) {
clearInterval(intervalId);
console.log("Interval stopped.");
}
}, 1000);
Tick 1 at 1.0s
Tick 2 at 2.0s
Tick 3 at 3.0s
Tick 4 at 4.0s
Tick 5 at 5.0s
Interval stopped.
Timer Execution Flow Diagram
Timeline ──────────────────────────────────────────────────────────▶
0ms 1ms 2000ms 2001ms
CALL STACK:
│setTimeout()│log("After")│ empty... │callback()│
WEB API (Timer):
│────── counting 2000ms ──────│
│ time expired
CALLBACK QUEUE: │
│callback in queue│──▶ to stack
EVENT LOOP: "Stack empty? Move callback from queue to stack!"
Fetch API — Network Requests
The fetch() function is a Web API that handles HTTP networking:
console.log("1. Starting fetch");
// fetch() is a Web API — network happens outside JS engine
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(response => response.json())
.then(post => {
console.log("3. Got post:", post.title.substring(0, 30));
});
console.log("2. After fetch() call (runs immediately!)");
1. Starting fetch
2. After fetch() call (runs immediately!)
3. Got post: sunt aut facere repellat provident
Note: Line 2 prints BEFORE line 3, proving that fetch() hands off to the Web API and JavaScript continues immediately.
DOM Event APIs — addEventListener
DOM events are also Web APIs — the browser monitors for user actions and queues callbacks:
// addEventListener registers with the DOM Events Web API
document.addEventListener("DOMContentLoaded", () => {
console.log("DOM fully loaded and parsed");
});
document.addEventListener("click", (event) => {
console.log(`Clicked at (${event.clientX}, ${event.clientY})`);
console.log("Target element:", event.target.tagName);
});
window.addEventListener("resize", () => {
console.log(`Window resized to ${window.innerWidth}x${window.innerHeight}`);
});
// Keyboard events
document.addEventListener("keydown", (event) => {
console.log(`Key pressed: ${event.key} (code: ${event.code})`);
});
Geolocation API
// Ask for user's location via Web API
function getLocation() {
if (!navigator.geolocation) {
console.log("Geolocation not supported");
return;
}
console.log("Requesting location...");
navigator.geolocation.getCurrentPosition(
// Success callback — fires via callback queue
(position) => {
const { latitude, longitude, accuracy } = position.coords;
console.log(`Location: ${latitude}, ${longitude}`);
console.log(`Accuracy: ${accuracy} meters`);
},
// Error callback
(error) => {
console.error("Location error:", error.message);
},
// Options
{ enableHighAccuracy: true, timeout: 5000 }
);
console.log("Location request sent (non-blocking!)");
}
getLocation();
Requesting location...
Location request sent (non-blocking!)
Location: 19.0760, 72.8777
Accuracy: 15 meters
requestAnimationFrame — Animation Web API
requestAnimationFrame schedules code to run just before the browser's next paint cycle — perfect for smooth animations:
let position = 0;
let animationId;
function animate(timestamp) {
position += 2; // Move 2px per frame
// Update the DOM element
const box = document.getElementById("box");
if (box) {
box.style.transform = `translateX(${position}px)`;
}
if (position < 300) {
// Schedule next frame (callback via Web API)
animationId = requestAnimationFrame(animate);
} else {
console.log("Animation complete!");
cancelAnimationFrame(animationId);
}
}
// Start animation
requestAnimationFrame(animate);
// Compared to setInterval approach (worse):
// setInterval(() => { position += 2; }, 16); // Imprecise timing
Animation complete! ← after ~150 frames at 60fps
Why rAF is better than setTimeout for animations:
┌────────────────────────────────────────────────────────────┐
│ requestAnimationFrame vs setTimeout for animations: │
│ │
│ requestAnimationFrame: │
│ • Synced with browser repaint cycle (~16.7ms at 60fps) │
│ • Pauses when tab is not visible (saves battery) │
│ • More accurate timing │
│ • No frame drops from timing drift │
│ │
│ setTimeout(fn, 16): │
│ • May drift out of sync with repaint │
│ • Continues running in background tabs (wastes battery) │
│ • Can cause visual tearing or dropped frames │
└────────────────────────────────────────────────────────────┘
Web Workers — True Parallelism
Web Workers run JavaScript in a separate thread — they're not in the call stack at all:
// main.js — Main thread
console.log("Main: Starting heavy computation via worker");
const worker = new Worker("worker.js");
// Listen for results from the worker
worker.addEventListener("message", (event) => {
console.log("Main: Worker result:", event.data);
worker.terminate();
});
// Send data to the worker
worker.postMessage({ start: 0, end: 1_000_000_000 });
console.log("Main: UI is still responsive while worker runs!");
// worker.js — Separate thread
self.addEventListener("message", (event) => {
const { start, end } = event.data;
let sum = 0;
for (let i = start; i < end; i++) {
sum += Math.sqrt(i);
}
// Send result back to main thread
self.postMessage(sum);
});
Main: Starting heavy computation via worker
Main: UI is still responsive while worker runs!
Main: Worker result: 666666665.8333334 ← arrives after computation
Web Workers vs Regular Code:
┌──────────────────────────────────────────────────────────────┐
│ MAIN THREAD: │
│ Call Stack → Web APIs → Callback Queue → Event Loop │
│ (Single-threaded, handles UI, DOM, event handling) │
│ │
│ WEB WORKER THREAD: │
│ Separate thread, NO DOM access │
│ Can run heavy computations without blocking UI │
│ Communicates via postMessage() │
└──────────────────────────────────────────────────────────────┘
Node.js Web APIs (libuv)
In Node.js, there are no browser Web APIs, but Node.js provides equivalent functionality through libuv (a C++ library):
// Node.js Web API equivalents via libuv:
const fs = require("fs");
const https = require("https");
// File system operations (libuv thread pool)
fs.readFile("data.txt", "utf8", (err, data) => {
// Callback via callback queue
if (err) return console.error("Error:", err.message);
console.log("File contents:", data.substring(0, 50));
});
// Network (libuv epoll/kqueue)
https.get("https://jsonplaceholder.typicode.com/posts/1", (response) => {
let data = "";
response.on("data", chunk => data += chunk);
response.on("end", () => {
const post = JSON.parse(data);
console.log("Post:", post.title.substring(0, 30));
});
});
// Timers (libuv timer handles)
setTimeout(() => console.log("Timer via libuv"), 1000);
Common Mistakes ⚠️
Mistake 1: Thinking setTimeout is Instant
// ❌ Wrong: setTimeout(fn, 0) does NOT run immediately
console.log("sync");
setTimeout(() => console.log("async"), 0);
console.log("sync again");
// Expected: sync → async → sync again
// Actual: sync → sync again → async
Mistake 2: Forgetting Web APIs Are External
// ❌ Misconception: JavaScript handles timers
// ✅ Reality: The BROWSER handles timers, JavaScript just registers the callback
// When you call setTimeout(), JavaScript says to the browser:
// "Hey browser, start a 1000ms timer and call this function when done"
// JavaScript moves on immediately — it doesn't wait
Mistake 3: DOM Manipulation in Web Workers
// ❌ Web Workers cannot access the DOM!
// In worker.js:
document.getElementById("output").textContent = "Done!"; // ReferenceError!
// ✅ Send a message to the main thread to do DOM work
self.postMessage({ action: "updateDOM", text: "Done!" });
// In main.js, receive the message and do the DOM update
Interview Questions 🎯
Q1. What are Web APIs in JavaScript?
Web APIs are interfaces provided by the browser (or Node.js via libuv) that allow JavaScript to perform operations outside the JavaScript engine's scope: timers, network requests, DOM manipulation, geolocation, storage, and more. They run on separate threads from the JavaScript engine, enabling non-blocking async behavior.
Q2. Does JavaScript directly handle setTimeout?
No. When you call setTimeout(), JavaScript hands the timer to the browser's Web API (Timer API). The browser tracks the countdown on a separate thread. When time expires, the callback is placed in the callback queue. JavaScript only picks it up when the call stack is empty.
Q3. What is the difference between Web APIs and the JavaScript engine?
The JavaScript engine (e.g., V8) handles parsing, compiling, and executing JavaScript code via the call stack and heap. Web APIs are provided by the browser/runtime and handle async operations (timers, network, DOM events) on separate threads. The engine doesn't know how to make HTTP requests — it asks the browser's network Web API to do it.
Q4. What is requestAnimationFrame and when should you use it?
requestAnimationFrame(callback) schedules a callback to run just before the browser's next repaint (~16ms for 60fps). Use it for animations, canvas drawing, or any visual updates. It's better than setInterval because it syncs with the render cycle, pauses when the tab is inactive (saving battery), and prevents visual artifacts.
Q5. What are Web Workers and how do they differ from regular async?
Web Workers run JavaScript code in a completely separate thread from the main thread. Unlike setTimeout or fetch (which use Web APIs but still execute callbacks on the main thread), Web Worker code runs in parallel on its own thread. Workers can't access the DOM but can handle heavy computation without blocking the UI. They communicate with the main thread via postMessage().
Q6. Why can't Web Workers access the DOM?
The DOM is not thread-safe. Allowing multiple threads to read and modify the DOM simultaneously would cause race conditions and unpredictable behavior. Web Workers were designed to run computation, not UI work. Communication happens through the safe postMessage channel.
Q7. What Web APIs are available in Node.js?
Node.js doesn't have browser Web APIs, but provides equivalents through libuv: file system operations (fs module), network (http, https, net modules), timers (setTimeout, setInterval), and DNS resolution. Node.js also provides worker_threads module as an equivalent to Web Workers.
Key Takeaways 📌
- Web APIs are provided by the browser — not part of the JavaScript engine
- They run outside the JavaScript thread, often on separate OS threads
- JS delegates async work to Web APIs and continues immediately — non-blocking
- When done, Web APIs push callbacks to the callback queue
- The event loop connects everything: checks when stack is empty, moves callbacks in
requestAnimationFrame is better than setInterval for animations (synced with render)- Web Workers provide true parallelism but can't touch the DOM
- Node.js uses libuv as its equivalent of browser Web APIs