When should AI-generated `.then()` chains be rewritten as async/await?

The item shows a candidate an AI-generated function that chains .then() and .catch() calls four levels deep, threading a value through callbacks that each transform it, and asks the candidate when (and how) to refactor the snippet into idiomatic async/await. The question probes whether the candidate can recognize that AI assistants — especially ones whose training data includes a long tail of pre-2018 tutorials — sometimes default to verbose promise-chain syntax even when the surrounding codebase has already converted to async/await.

What this question tests

The concept under test is the trade-off between two control-flow styles for async JavaScript: explicit promise chains versus async/await, and the cases where one is correct and the other is a stylistic mismatch. The ECMAScript specification defines both: promises are objects that represent eventual completion or failure of an async operation, and async/await is syntactic sugar over promises that lets the function read top-to-bottom in a synchronous-looking style while preserving the same underlying semantics. AI assistants generate both styles, and they don’t always pick the style that matches the surrounding code.

The question targets candidates who treat AI suggestions as drafts and reshape them to match codebase conventions, not candidates who paste suggestions verbatim. It also probes whether the candidate knows the cases where promise chains are genuinely better — primarily concurrent fan-out with Promise.all, where await on a sequence of promises that were already kicked off is correct, but writing one await per call in a loop serializes work that could have run in parallel.

Why this is the right answer

The correct answer has two parts: identify when AI-generated chains should be rewritten, and when they should be left alone. A linear chain of dependent steps reads more clearly as async/await:

// AI-generated promise chain
function loadUserProfile(id) {
  return fetchUser(id)
    .then((user) => fetchOrders(user.id)
      .then((orders) => fetchPayments(orders[0].id)
        .then((payments) => ({ user, orders, payments }))));
}

// Idiomatic async/await
async function loadUserProfile(id) {
  const user = await fetchUser(id);
  const orders = await fetchOrders(user.id);
  const payments = await fetchPayments(orders[0].id);
  return { user, orders, payments };
}

The async/await version is structurally identical in behavior — three sequential awaits, each blocking on the prior result — but the indentation no longer pyramids and error handling can use a single try/catch rather than a trailing .catch() whose scope is hard to read.

The cases where the chain form is right are concurrent fan-out patterns:

// Correct: kick off in parallel, await the array
async function loadDashboard(id) {
  const [user, settings, notifications] = await Promise.all([
    fetchUser(id),
    fetchSettings(id),
    fetchNotifications(id),
  ]);
  return { user, settings, notifications };
}

// Wrong: serializes work that could run in parallel
async function loadDashboard(id) {
  const user = await fetchUser(id);
  const settings = await fetchSettings(id);
  const notifications = await fetchNotifications(id);
  return { user, settings, notifications };
}

The senior-level review skill is recognizing that async/await is not strictly better than promise chains; it is better for sequential dependent work and worse (or at least no different) for parallel independent work, where Promise.all is the right primitive and the awaits inside must be on the array, not on each individual promise.

What the wrong answers reveal

The plausible wrong options each map onto a different gap in async-JavaScript fluency:

  • “Always rewrite to async/await; promise chains are legacy.” This option treats stylistic preference as semantic equivalence. Promise chains are not legacy, and Promise.all, Promise.race, Promise.allSettled, and Promise.any are first-class members of the spec that read most naturally as chains. Picking this option suggests the candidate has cargo-culted “async/await is modern” without understanding the trade-offs.
  • “Never rewrite; the AI knew what it was doing.” This abdicates the review responsibility that defines real AI-augmented work. AI assistants have biases inherited from training data; one of those biases is over-generation of promise-chain syntax even in async/await-first codebases. Picking this option suggests the candidate is pasting suggestions without review.
  • “Rewrite to a for...of loop with await inside.” This is a specific anti-pattern: await in a for...of loop serializes calls that could run concurrently. It is occasionally correct (when each iteration genuinely depends on the previous result), but as a default replacement for promise chains it makes performance worse, not better.

How the sample test scores you

In the AIEH 5-question AI-Augmented JavaScript sample, this item contributes one of five datapoints aggregated into a single ai_js_proficiency score via the W3.2 normalize-by-count threshold. Binary scoring per item: 5 for the correct option, 1 for any of the three wrong options. With 5 binary items, the average ranges 1–5 and the level threshold maps avg ≤ 2 to low, ≤ 4 to mid, > 4 to high.

Data Notice: Sample-test results are directional indicators only. A 5-question sample can’t reliably distinguish between “knows when to refactor AI-generated async code” and “got lucky on these specific items”; for a verified Skills Passport credential, take the full 50-question assessment.

The full assessment probes async control flow, error handling in try/catch/finally, top-level await, AbortController, and the specific gotchas (unhandled rejection, accidental serialization, await in forEach) at depth. See the scoring methodology for how AI-Augmented JavaScript scores map onto the AIEH 300–850 Skills Passport scale.

  • Unhandled rejection semantics. A promise that rejects without an attached .catch() triggers an unhandledrejection event in browsers and crashes Node.js by default in modern versions. AI-generated chains that forget the trailing .catch produce silent failures in test runs and crashes in production. Reviewing for this is part of the AI-augmented review loop.
  • finally and resource cleanup. Both promise chains (.finally()) and async/await (try/finally) support cleanup that runs whether the operation succeeded or failed. AI-generated code is inconsistent about including cleanup; reviewing for it is part of robust async work.
  • Top-level await in modules. Modern ESM modules support await at the top level, which AI assistants sometimes miss when generating module-level fetches. The result is a wrapping (async () => { ... })() IIFE that adds noise.

For the broader AI-Augmented JavaScript lineup including the full 50-question assessment, see the tests catalog and the frontend engineering interview prep guide. Hiring managers building rubrics around async fluency should also see hire for assessment products.


Sources

Try the question yourself

This explainer covers what the item measures. To see how you score on the full ai augmented javascript family, take the free 5-question sample.

Take the ai augmented javascript sample