A promise represents the eventual completion or failure of an operation. Its states are pending, fulfilled and rejected. Attaching a handler does not make the result available synchronously. An async function always returns a promise; await pauses that async function's continuation while allowing the runtime to do other work. It does not block the entire application until the result arrives.
Predict a small schedule
const events = [];
events.push("start");
Promise.resolve().then(() => events.push("promise"));
setTimeout(() => {
events.push("timer");
console.log(events.join(" > "));
}, 0);
events.push("end");
This example prints start > end > promise > timer. Current synchronous code finishes before a promise reaction runs. Promise reactions use microtasks; a timer callback runs as later scheduled work. A zero delay is not a promise of immediate execution or an exact wall-clock deadline. Browser and Node scheduling have additional host-specific details, so do not extrapolate this small example into a universal ordering rule for every kind of I/O.
Long synchronous CPU work still blocks the thread executing it. Adding async to a function does not move a large calculation to another thread. Consider splitting work or using an appropriate worker facility when the workload genuinely needs it. An unbounded chain of microtasks can also delay other work.
Make concurrency intentional
async function readSummary(id) {
if (id === "bad") throw new Error("Unknown summary");
return { id, minutes: id.length * 5 };
}
const ordered = await Promise.all([readSummary("js"), readSummary("typescript")]);
console.log(ordered.map(item => item.minutes).join(","));
const outcomes = await Promise.allSettled([readSummary("js"), readSummary("bad")]);
console.log(outcomes.map(item => item.status).join(","));
The lines are 10,50 and fulfilled,rejected. Promise.all preserves input order in its result and rejects if an input rejects; that rejection does not automatically cancel the remaining operations. allSettled reports each outcome when partial results are useful. Await operations in a loop when later work depends on earlier results. Starting thousands of requests at once is not responsible concurrency; use a bounded pool when required.
Own every rejection
Return or await an asynchronous operation so its caller can observe failure. A forgotten promise inside a callback can leave work running after its caller thinks it has finished. forEach does not await an async callback. Use an explicit for...of loop for sequential work or build an array of promises for a chosen concurrent strategy.
A try/catch around await handles a rejected promise. When using .then, return the next promise from the callback to connect the chain. A .catch that returns a value converts the chain into fulfillment with that value, so swallowing an error can create a false success. Cleanup belongs in finally when it must happen on either outcome.
Exercise: make one input fail in the Promise.all example. Handle the failure, then explain whether a second operation has been canceled. Repeat using allSettled and count only fulfilled results.
Solution guidance: wrap the awaited aggregate in try/catch and inspect each settled result's status before accessing its value. Cancellation is a separate cooperative contract, introduced in the next lesson.
References: Using promises, execution model.
Previous: Errors, Validation and Reliable Boundaries. Next: Fetch, HTTP Errors and Cancellation.

