From the ai augmented javascript sample test
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, andPromise.all,Promise.race,Promise.allSettled, andPromise.anyare 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...ofloop withawaitinside.” This is a specific anti-pattern:awaitin afor...ofloop 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.
Related concepts
- Unhandled rejection semantics. A promise that rejects
without an attached
.catch()triggers anunhandledrejectionevent in browsers and crashes Node.js by default in modern versions. AI-generated chains that forget the trailing.catchproduce silent failures in test runs and crashes in production. Reviewing for this is part of the AI-augmented review loop. finallyand resource cleanup. Both promise chains (.finally()) andasync/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
awaitin modules. Modern ESM modules supportawaitat 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
- Ecma International. (2024). ECMAScript 2024 Language Specification (ECMA-262, 15th edition). — Section 27 covers promises; Section 27.7 covers async functions. https://tc39.es/ecma262/
- MDN Contributors. (2024). Using promises — JavaScript Reference. Mozilla Developer Network. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
- MDN Contributors. (2024). async function — JavaScript Reference. Mozilla Developer Network. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
- Node.js Foundation. (2024). Node.js Documentation: Process — Event: ‘unhandledRejection’. https://nodejs.org/api/process.html#event-unhandledrejection