JavaScript is single-threaded, but it handles asynchronous work through the event loop, callbacks, promises, and async/await.
- Network requests take time
- Timers execute later
- File-like browser operations may complete later
- UI should stay responsive during waiting
A callback is a function passed into another function to run later.
setTimeout(() => {
console.log("Later");
}, 1000);function fetchData(callback) {
callback("Data ready");
}- Nested callbacks can become hard to read
- Error handling gets messy
- Reuse is harder
This is why promises and async/await became the preferred style for most modern async code.
A promise represents a value that may be available now, later, or never.
- pending
- fulfilled
- rejected
const p = new Promise((resolve, reject) => {
const success = true;
if (success) {
resolve("Done");
} else {
reject("Failed");
}
});p.then(result => {
console.log(result);
}).catch(error => {
console.error(error);
}).finally(() => {
console.log("Finished");
});fetch("/api/user")
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));Promise.all: waits for all promises and fails fast if one rejectsPromise.allSettled: waits for all and reports every resultPromise.race: settles as soon as one settlesPromise.any: resolves when the first promise fulfills
Promise.all([p1, p2, p3]);- Promises flatten callback chains.
- They are the foundation for modern async/await syntax.
async/await is syntax built on top of promises.
async function loadUser() {
try {
const response = await fetch("/api/user");
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}asyncmakes a function return a promise.awaitpauses inside an async function until the promise settles.awaitcan only be used insideasyncfunctions, unless top-level await is supported in the environment.
- Easier to read
- Easier to reason about
- Cleaner error handling with
try/catch
If two async operations do not depend on each other, start them together.
const [user, posts] = await Promise.all([
fetch("/api/user").then(r => r.json()),
fetch("/api/posts").then(r => r.json())
]);Good async code always plans for failure.
try {
const response = await fetch("/api/data");
if (!response.ok) {
throw new Error("Request failed");
}
const data = await response.json();
} catch (error) {
console.error("Something went wrong", error);
}fetch("/api/data")
.then(response => {
if (!response.ok) throw new Error("Request failed");
return response.json();
})
.catch(error => {
console.error(error);
});- Check
response.okfor network requests - Throw meaningful errors
- Avoid swallowing errors silently
- Use
finallyfor cleanup
- Network failure
- Invalid JSON
- Permission denied
- Timeout behavior
- Unexpected response shape