Leverage the event loop, parallelize your promises correctly, and make asynchronous error handling reliable.
Open this lesson in KodokonThe execution model relies on a macrotask queue (setTimeout, I/O) and a microtask queue (.then, await, queueMicrotask). After each task, the engine drains the microtask queue completely before moving on. Direct consequence: a resolved promise always runs before a setTimeout(fn, 0), and an overly long chain of microtasks can freeze rendering just as surely as a synchronous loop.
console.log("start");
setTimeout(() => console.log("macrotask"), 0);
Promise.resolve().then(() => console.log("microtask"));
console.log("end");
// start, end, microtask, macrotaskasync/await is sugar on top of promises, but it hides a performance trap: each await inside a loop serializes the operations. If the requests are independent, start them all first, then wait for the batch with Promise.all. For three 100 ms requests, the sequential version costs 300 ms, the parallel version 100 ms.
const fetchUser = (id) => new Promise((res) => {
setTimeout(() => res({ id }), 100);
});
async function loadSequential(ids) {
const users = [];
for (const id of ids) {
users.push(await fetchUser(id)); // ~100 ms each
}
return users;
}
async function loadParallel(ids) {
return Promise.all(ids.map(fetchUser)); // ~100 ms total
}Pick the right combinator for the failure contract you need: Promise.all rejects on the first failure (all-or-nothing), allSettled waits for everyone and describes each outcome, race takes the first settlement (success or failure, useful for a timeout), any takes the first success and only rejects if everything fails. For a dashboard that must show partial results, allSettled is almost always the right choice.
const tasks = [
Promise.resolve("profile loaded"),
Promise.reject(new Error("network unavailable")),
];
const results = await Promise.allSettled(tasks);
for (const r of results) {
if (r.status === "fulfilled") {
console.log("success:", r.value);
} else {
console.error("failure:", r.reason.message);
}
}console.log, a Promise.resolve().then callback, and a setTimeout(fn, 0) callback run?try { doAsync(); } catch (e) {} without await fail to catch the rejection from doAsync?