5.0 The Problem: Blocking Code
JavaScript runs on a single thread. A long synchronous operation - reading a file,
querying a database, waiting for a network response - blocks every other operation
until it finishes. The page freezes; the user sees no response to clicks or key presses.
// bad: synchronous network (hypothetical - blocks the thread)
const data = fetchSync('/api/items'); // nothing else can run here
render(data);
JavaScript solves this with asynchronous patterns that let the engine
continue processing other events while waiting for a slow operation to complete.
Three patterns exist, in historical order: callbacks, Promises, and
async/await.
5.1 Callbacks
The original async pattern: pass a function to be called when the operation completes.
setTimeout(() => {
console.log('ran after 1 second');
}, 1000);
// Node-style error-first callback
fs.readFile('data.txt', 'utf8', (err, text) => {
if (err) { console.error(err); return; }
console.log(text);
});
Callbacks work, but nesting them for sequential async steps produces deeply indented
code known as callback hell:
getUser(id, (err, user) => {
if (err) return handleErr(err);
getOrders(user.id, (err, orders) => {
if (err) return handleErr(err);
getItems(orders[0].id, (err, items) => {
if (err) return handleErr(err);
render(items); // three levels deep, errors repeated at each level
});
});
});
5.2 Promises
A Promise is an object representing the eventual result of an async
operation. It is always in one of three states: pending,
fulfilled (succeeded with a value), or rejected
(failed with a reason).
5.2.1 Creating a Promise
function delay(ms) {
return new Promise((resolve, reject) => {
if (ms < 0) { reject(new Error('negative delay')); return; }
setTimeout(() => resolve(ms), ms);
});
}
delay(500).then(ms => console.log(`done after ${ms}ms`));
5.2.2 Chaining
.then(onFulfilled) returns a new Promise, enabling flat sequential chains
that replace nested callbacks:
getUser(id)
.then(user => getOrders(user.id))
.then(orders => getItems(orders[0].id))
.then(items => render(items))
.catch(err => handleErr(err)); // one error handler for the whole chain
5.2.3 Promise Combinators
Promise.all, race, allSettled, any
| Combinator |
Behavior |
Promise.all(iterable) |
Fulfills when all fulfill; rejects as soon as any one rejects. Result is an array of values in input order. |
Promise.allSettled(iterable) |
Always fulfills when every Promise settles. Result is an array of {status, value|reason} objects - no early rejection. |
Promise.race(iterable) |
Settles with the first Promise to settle, fulfilled or rejected. |
Promise.any(iterable) |
Fulfills with the first fulfilled value. Rejects with AggregateError only if all reject. |
Promise.resolve(value) |
Returns a Promise already fulfilled with value. |
Promise.reject(reason) |
Returns a Promise already rejected with reason. |
// fetch three resources in parallel, wait for all
const [users, posts, tags] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
fetch('/api/tags').then(r => r.json()),
]);
5.3 async / await
async/await is syntactic sugar over Promises that lets you
write async code with the same linear structure as synchronous code. Marking a function
async makes it always return a Promise. await expr pauses the
function (not the thread) until the Promise resolves, then evaluates to the fulfilled value.
async function loadDashboard(userId) {
const user = await getUser(userId);
const orders = await getOrders(user.id);
const items = await getItems(orders[0].id);
render(items);
}
// reads top-to-bottom; equivalent to the .then chain above
5.4 Error Handling
Use try/catch inside async functions:
async function loadDashboard(userId) {
try {
const user = await getUser(userId);
const orders = await getOrders(user.id);
render(orders);
} catch (err) {
showError(err.message);
} finally {
hideSpinner();
}
}
Always handle rejections. An unhandled Promise rejection triggers the browser's
unhandledrejection event:
window.addEventListener('unhandledrejection', e => {
console.error('Unhandled rejection:', e.reason);
});
5.5 The Fetch API
fetch(url, options) is the standard way to make HTTP requests from the
browser. It returns a Promise for a Response object. Key detail: fetch
only rejects on network failure - always check resp.ok for HTTP
error status codes like 404 or 500.
async function getItems() {
const resp = await fetch('/api/items');
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
return resp.json(); // .json() also returns a Promise
}
// POST with JSON body
const resp = await fetch('/api/items', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'widget' }),
});
Common fetch options and Response members
| Member |
Purpose |
| fetch() options |
method | HTTP verb: GET (default), POST, PUT, PATCH, DELETE. |
headers | Object or Headers instance of request headers. |
body | Request body: string, FormData, Blob, ReadableStream. Not allowed on GET/HEAD. |
credentials | omit | same-origin (default) | include - whether to send cookies cross-origin. |
mode | cors (default) | no-cors | same-origin - CORS policy. |
cache | default | no-store | reload | force-cache. |
signal | AbortSignal from an AbortController to cancel the request. |
| Response members |
resp.ok | true when status is 200-299. |
resp.status | HTTP status code (200, 404, 500, ...). |
resp.json() | Parse body as JSON; returns a Promise. |
resp.text() | Read body as a UTF-8 string; returns a Promise. |
resp.blob() | Read body as a Blob; returns a Promise. |
resp.arrayBuffer() | Read body as an ArrayBuffer; returns a Promise. |
5.5.1 Cancelling a Request
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const resp = await fetch('/api/slow', { signal: controller.signal });
render(await resp.json());
} catch (err) {
if (err.name === 'AbortError') console.log('request was cancelled');
else throw err;
} finally {
clearTimeout(timeout);
}
5.6 Common Async Patterns
Sequential steps - each step uses the result of the previous:
const a = await step1();
const b = await step2(a);
const c = await step3(b);
Parallel requests - all start at once, wait for all to finish:
const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()]);
Retry on failure - attempt up to n times before re-throwing:
async function withRetry(fn, attempts = 3) {
for (let i = 0; i < attempts; i++) {
try { return await fn(); }
catch (e) { if (i === attempts - 1) throw e; }
}
}
Debounce - defer async work until the user stops typing:
let timer;
input.addEventListener('input', () => {
clearTimeout(timer);
timer = setTimeout(() => search(input.value), 300);
});
Sequential array processing - one item at a time to avoid overload:
for (const id of ids) {
await processOne(id); // waits for each before starting the next
}
Parallel array processing - all items at once:
await Promise.all(ids.map(id => processOne(id)));
5.7 Tutorials
Async JavaScript Tutorials
| Tutorial |
Type |
Why Use It |
| MDN: Asynchronous JavaScript |
Beginner→Intermediate |
Complete series from callbacks through async/await with live examples. |
| javascript.info: Async/await |
Intermediate |
Clear, concise explanations with runnable examples for each concept. |
| MDN: Using the Fetch API |
Intermediate |
Comprehensive guide to fetch options, response handling, and error patterns. |
| javascript.info: Promises |
Beginner→Intermediate |
Four-part series covering creation, chaining, error handling, and combinators. |
| web.dev: JavaScript Promises |
Intermediate |
Thorough treatment of promise states, chaining, and parallelism. |
| Jake Archibald: Event Loop (talk) |
Deep dive |
Visual explanation of how the event loop, tasks, and microtasks interact. |
5.8 References
Async JavaScript References
| Resource |
Description |
| MDN: Promise |
Full API reference for Promise, including all static methods and instance methods. |
| MDN: async function |
Reference for async function syntax, return values, and interaction with await. |
| MDN: Fetch API |
Complete reference for fetch(), Request, Response, and Headers. |
| MDN: AbortController |
API for cancelling fetch requests and other async operations. |
| ECMAScript spec: Promises |
Formal specification of the Promise abstract operations and state machine. |
| WHATWG: Event Loops |
Authoritative specification of the event loop, task queues, and microtask checkpoint. |