CodeRigor

FREE LESSON

Promises, async functions and ordering

Await sequential work, settle independent failures and preserve input order without timing assumptions.

A promise represents an eventual fulfillment or rejection. Calling an async function immediately returns a promise; its body begins running until it reaches a suspension point. await observes a promise's eventual result and resumes the function later. A rejection becomes a thrown value at the await expression, so ordinary try/catch can define the recovery policy.

JavaScript
async function example() {
  console.log("start");
  await Promise.resolve();
  console.log("resume");
}
example();
console.log("outside");

The output is start, outside, resume. Awaiting an already fulfilled promise still suspends the continuation. Promise reactions run after the current synchronous work; this does not imply that timers and I/O callbacks have one universal ordering across all program contexts. The exercises use promises and controlled resolution rather than racing unrelated timers.

Await creates a causal sequence
start / 4
Series work completes one operation before the next starts.
Await creates a causal sequence
Series work completes one operation before the next starts.

Sequential and concurrent work have different contracts. A loop that awaits each worker starts the next item only after the current one settles. If a failure should stop later work, let that rejection escape. A loop that merely pushes worker promises starts more work before earlier operations finish and returns a collection of promises instead of settled values.

forEach does not await async callbacks. Marking the callback async does not change forEach's return contract. Prefer an explicit loop for sequential work, or construct a promise array and await Promise.all for independent operations. Choose deliberately according to dependencies and failure policy.

JavaScript
async function series(values) {
  const result = [];
  for (const value of values) result.push(await Promise.resolve(value * 2));
  return result;
}
series([3, 1]).then(values => console.log(JSON.stringify(values)));
Result order follows input order
start / 1
Resolve the second promise first; the aggregate keeps both original positions.
Result order follows input order
Resolve the second promise first; the aggregate keeps both original positions.

Promise.all preserves the input ordering of results, even when operations complete in another order. It rejects if an input rejects, and it does not cancel operations that are already running. If each failure should become an outcome record instead, catch inside each operation before aggregating. This is the policy of settleAll: every input position has a success or failure record.

A synchronous throw from a worker and an asynchronous rejection both need coverage. An async wrapper converts a synchronous throw into a rejected promise, and its local catch can turn either failure into data. Do not forget the await inside that try: returning an unobserved worker promise lets rejection happen outside the intended catch boundary.

Test causality, not speed. Keep an event list for series execution and verify start/end pairs. For concurrent settlement, create promises whose resolver functions the test controls; resolve the second before the first and check that result positions still match the inputs. These are real promise operations inside Node, but they are local deterministic fixtures. HTTP, cancellation, retries, timeouts and production concurrency limits require additional interfaces beyond this introductory lesson.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Watch promise creation, then/catch and Promise.all. Apply the distinction between failure propagation and per-item recovery to the two build functions.