Promises and async/await
Handle work that finishes later: timers, fetch and error handling.
What you will learn
- Explain the event loop
- Use async/await
- Run requests in parallel
JavaScript runs your code on a single thread. To stay responsive while waiting for things like network requests or timers, it hands slow work to the environment and continues. When the result is ready, a callback is queued and runs once the current code has finished. This mechanism is the event loop.
console.log("1 start");
setTimeout(() => console.log("3 timer"), 0);
console.log("2 end");1 start 2 end 3 timer
Even with a delay of 0, the timer callback runs after the synchronous code finishes.
Promises
A promise stands for a value that will arrive later. It is pending, then either fulfilled with a value or rejected with an error. Chain .then() for success and .catch() for failure.
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
wait(100)
.then(() => console.log("done waiting"))
.catch((err) => console.error(err));async / await
async functions always return a promise, and await pauses the function until a promise settles, so asynchronous code reads top to bottom. Wrap awaits in try/catch to handle errors.
async function loadUser(id) {
try {
const res = await fetch(`https://api.example.com/users/${id}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
console.error("failed:", err.message);
return null;
}
}
loadUser(1).then(console.log);fetch only rejects on network failure. A 404 or 500 still resolves, so always check res.ok.
Parallel work
Awaiting things one after another makes them run in sequence. When the calls are independent, start them together with Promise.all, which resolves when all are done and rejects if any fails.
const wait = (ms, v) => new Promise((r) => setTimeout(() => r(v), ms));
async function main() {
console.time("parallel");
const [a, b] = await Promise.all([wait(200, "A"), wait(200, "B")]);
console.timeEnd("parallel"); // ~200ms, not 400ms
console.log(a, b);
}
main();parallel: 201ms A B
Promise.allSettled waits for every promise and reports each outcome, which is handy when partial failure is acceptable.
Common mistakes
- Forgetting
await, so you log a pending promise instead of its value. - Using
awaitinsideforEach; it does not wait. Use afor...ofloop orPromise.allwithmap. - Not handling rejections, which produce "unhandled promise rejection" errors.
Try it yourself
Write retry(fn, times), an async function that calls fn and, if it throws, tries again up to times attempts before giving up.
Show solution
async function retry(fn, times) {
let lastErr;
for (let i = 0; i < times; i++) {
try {
return await fn();
} catch (err) {
lastErr = err;
}
}
throw lastErr;
}