{"id":"code-dev-js-foreach-async-sequential","audience":"developers","category":"coding","track":"coding","technique":"context","title":"Fix an async forEach without breaking the rate limit","free":false,"task":"syncAll returns before any upload finishes because of forEach with an async callback. Fix it so it awaits each upload in order with for...of. The partner API allows 4 requests a second, so it must stay sequential (no Promise.all) and await the existing sleep(250) after each upload. Keep the name, parameter and results array. Ops also wants failures logged with console.error(`sync: upload failed for ${item.id}`, err) instead of console.warn. Return only syncAll in exactly 1 code block of at most 16 lines, under 20 words of text.","materialLabel":"The module (attached to your prompt automatically)","material":"// src/sync.js  (Node 20, ESM) - pushes local menu items to the Tablehop partner API\nimport { uploadItem } from \"./partnerApi.js\";\n\nconst sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\n\nexport async function syncAll(items) {\n  const results = [];\n  items.forEach(async (item) => {\n    try {\n      const res = await uploadItem(item);\n      results.push({ id: item.id, ok: true, remoteId: res.id });\n    } catch (err) {\n      console.warn(`upload failed for ${item.id}: ${err.message}`);\n      results.push({ id: item.id, ok: false });\n    }\n  });\n  return results;\n}\n\n// Called from the admin \"Sync now\" button:\n//   const results = await syncAll(menuItems);\n//   showSummary(results);   // always shows \"0 of 37 synced\"\n//\n// partnerApi.js wraps fetch and throws on non-2xx responses.\n// Menu sizes: 20 to 60 items per restaurant.","rules":[{"type":"codeBlocks","count":1},{"type":"maxCodeLines","max":16},{"type":"maxProseWords","max":20}],"mistakes":[{"title":"Going parallel","why":"The usual fix is Promise.all, which fires 37 requests at once and gets the key paused.","fix":"Add: \"Keep it sequential with for...of; no Promise.all.\"","checks":["r0"]},{"title":"Ignoring the rate limit","why":"The AI can't know about the limit or the unused sleep helper unless you point at them.","fix":"Add: \"await sleep(250) after each upload.\"","checks":["r1"]},{"title":"Missing the new log line","why":"The AI keeps console.warn; it can't know ops wants console.error with a sync: prefix.","fix":"Add: \"In the catch: console.error(`sync: upload failed for ${item.id}`, err).\"","checks":["r2"]},{"title":"The whole file and a lesson","why":"The AI reprints the module and explains why forEach ignores promises.","fix":"Add: \"Only syncAll, one code block, no text.\"","checks":["codeBlocks","maxCodeLines","maxProseWords"]}],"promptHints":[{"label":"Asks for sequential calls","patterns":["for.{0,5}of","sequential","one at a time","no promise\\.all"]},{"label":"Mentions the rate limit","patterns":["rate limit","4 (requests|per)","sleep"]},{"label":"Changes the log line","patterns":["console\\.error","sync:"]},{"label":"Asks for only the function","patterns":["only (the )?syncAll","just the function","only the function"]},{"label":"Limits the reply","patterns":["no (text|explanation)","one code block"]}],"hint":"The obvious fix breaks something only you know about. Tell the AI about the rate limit, the helper that's already there, the new log line and what must stay the same.","rubricLabels":["Uses for (const item of items) with await uploadItem(item) inside, and no forEach, Promise.all or other parallel calls","Awaits sleep(250) after each upload (success or failure), so calls stay under 4 per second","The catch logs console.error(`sync: upload failed for ${item.id}`, err) instead of console.warn, and the function keeps export async function syncAll(items), the same result objects and return results"]}