pending
Initial state. Operation still running.
Asynchronous JavaScript lets your app continue running while waiting for slow tasks like API calls, file operations, or timers. Without async patterns, the UI can freeze and users must wait for every task to finish before doing anything else.
console.log("A");console.log("B");console.log("C");Each line waits for the previous line.
console.log("A");setTimeout(() => console.log("B"), 0);console.log("C");Output is A, C, then B because timeout callback runs later.
A callback is a function passed into another function and run later.
function getData(callback) { setTimeout(() => { callback("Data loaded"); }, 1000);}
getData((result) => { console.log(result);});Callbacks are simple and still useful, but deeply nested callbacks become hard to read.
Callback hell happens when many async steps depend on each other and create deep nesting.
loginUser((user) => { getProfile(user.id, (profile) => { getOrders(profile.id, (orders) => { getInvoice(orders[0].id, (invoice) => { console.log(invoice); }); }); });});A Promise represents a value that will be available now, later, or never. A promise has three states:
pending
Initial state. Operation still running.
fulfilled
Operation completed successfully.
rejected
Operation failed.
const wait = (ms) => { return new Promise((resolve, reject) => { if (typeof ms !== "number") { reject(new Error("ms must be a number")); return; }
setTimeout(() => { resolve(`Done in ${ms}ms`); }, ms); });};resolve marks success and passes data forward. reject marks failure and passes an error.
wait(500) .then((result) => { console.log(result); }) .catch((error) => { console.error(error.message); }) .finally(() => { console.log("Always runs"); });getUser() .then((user) => getOrders(user.id)) .then((orders) => getInvoice(orders[0].id)) .then((invoice) => console.log(invoice)) .catch((error) => console.error("Flow failed:", error));Chaining makes async steps linear and avoids deep callback nesting.
async and await are syntax built on promises. They make async code look close to synchronous code.
async function loadDashboard() { try { const user = await getUser(); const orders = await getOrders(user.id); console.log(orders); } catch (error) { console.error("Could not load dashboard", error); }}async function createPost() { const response = await fetch("https://example.com/api/posts", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer your-token" }, body: JSON.stringify({ title: "Hello", content: "Learning async deeply" }) });
const responseHeaders = response.headers; const contentType = responseHeaders.get("content-type"); const data = await response.json();
console.log("Response content-type:", contentType); console.log("Response body:", data);}app.post("/api/posts", express.json(), (req, res) => { console.log("Auth header:", req.headers.authorization); console.log("Request body:", req.body);
res.setHeader("x-app-version", "1.0.0"); res.json({ ok: true, received: req.body });});const xhr = new XMLHttpRequest();xhr.open("POST", "https://example.com/api/posts");xhr.setRequestHeader("Content-Type", "application/json");xhr.setRequestHeader("Authorization", "Bearer your-token");
xhr.onload = function () { console.log("Status:", xhr.status); console.log("Response headers:\n", xhr.getAllResponseHeaders()); console.log("Response body:", xhr.responseText);};
xhr.onerror = function () { console.error("Network error");};
xhr.send(JSON.stringify({ title: "Hello", content: "Sent via XHR" }));fetch is usually cleaner and promise-based. XHR is older but still appears in legacy code.
try { const response = await fetch("/api/data"); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); console.log(data);} catch (error) { console.error("Request failed:", error.message);}fetch("/api/data") .then((res) => { if (!res.ok) throw new Error("Bad response"); return res.json(); }) .catch((error) => { console.error(error.message); });class ApiError extends Error { constructor(message, status) { super(message); this.status = status; }}async/await for readability in complex flows.try/catch around each logical async unit.Content-Type, auth tokens).