Programming with AI
ENRU
9.2

Asynchrony in JavaScript

promises, await and four places where an AI gets it wrong

What it is

Asynchrony in JavaScript is arranged the same way as in Python (article 2.10): async, await, waiting instead of idling. But it has its own terminology and its own traps, and an AI reproduces all four below regularly.

And here is a direct parallel with chapter 6: Promise.all and Promise.allSettled relate to each other exactly as gather and gather(return_exceptions=True) do.

What it looks like in code

const fetchCity = (city, ms = 100) =>
  new Promise((resolve, reject) =>
    setTimeout(() => city === "Bergen"
      ? reject(new Error("server returned 500"))
      : resolve(`${city}: +4`), ms));

A promise is a promise of a result that will appear later. It has three states: pending, fulfilled, rejected. resolve is success, reject is an error.

Writing new Promise by hand is almost never necessary: promises come back from fetch and from libraries. But you do need to know what one is — the word comes up constantly.

1. Promise.all drops everything

await Promise.all([fetchCity("Oslo"), fetchCity("Bergen"), fetchCity("Tromsø")]);
measured
failed entirely: server returned 500

The requests to Oslo and Tromsø succeeded — their results are lost. Exactly the same as asyncio.gather without protection in Python.

2. Promise.allSettled keeps the successes

const r = await Promise.allSettled([...]);
for (const x of r)
  console.log(x.status === "fulfilled" ? `ok: ${x.value}` : `error: ${x.reason.message}`);
measured
ok: Oslo: +4
error: server returned 500
ok: Tromsø: +4

Each element is an object with a status. A successful one has a value, a failed one a reason. Checking status is mandatory: otherwise the error travels on as an ordinary value.

What you needWhat to take
All of them must succeed or there is no pointPromise.all
A partial result is better than nothingPromise.allSettled
The first successful answer is enoughPromise.any
The first answer of any kind counts, even an error (a race against a timeout)Promise.race

3. forEach doesn't wait

The sneakiest of the four:

const done = [];
["Oslo", "Tromsø"].forEach(async c => { done.push(await fetchCity(c)); });
console.log("right after forEach:", done);
measured
right after forEach: []
after 300 ms:        [ 'Oslo: +4', 'Tromsø: +4' ]

The array is empty. forEach cannot wait for async functions: it starts them all and moves on without waiting. The data does arrive — but later, once the code that needed it has already finished.

The right way is an ordinary loop:

for (const c of ["Oslo", "Tromsø"]) ok.push(await fetchCity(c));
measured
after the loop: [ 'Oslo: +4', 'Tromsø: +4' ]

Or Promise.all with map, if the requests are independent and you want them in parallel.

4. A forgotten await

measured
without await: Promise { <pending> }
with await:    Oslo: +4

Instead of a value, a promise object. In Python that is <coroutine object>, here Promise { <pending> } — the same symptom, a different word.

What to tell the AI

Prompt
These three requests are independent. I need a partial result:
if one server doesn't answer, the rest should still come
through. Use Promise.allSettled and handle status.
Prompt
This code has a forEach with async inside. Rewrite it so the
results are actually awaited: for...of if the requests depend
on each other, or Promise.all with map if they don't.
Prompt
Add a 5-second timeout to each request with
AbortSignal.timeout. On a timeout, a console warning and skip,
not a crash.

What to look for

Promise.all where a partial result is wanted. One error loses everything.

forEach(async ...). Doesn't wait. Always a mistake if the result is needed.

No .catch and no try. A rejected promise nobody caught gives an UnhandledPromiseRejection — in Node that means the process dies.

Sequential awaits in a loop where parallel would do. Not an error, but three times slower on three requests (article 2.10).

No timeout. A request can hang forever (article 6.4). Both the browser and Node have AbortSignal.timeout(ms) for that.

.then() mixed in with await. It works; it reads badly. New code uses await.

Cheat sheet

  • A promise is a promise of a result; resolve success, reject error.

  • Promise.all drops everything at the first error, allSettled keeps the successes.

  • With allSettled check status: fulfilled → value, rejected → reason.

  • forEach(async ...) doesn't wait. You want for...of or Promise.all(map).

  • Promise { <pending> } in the output means a forgotten await.

  • An uncaught rejected promise kills the process in Node.