JavaScript async review
JavaScript Async Code Review: Common Bugs AI-Generated Code Still Makes
Async bugs are easy to miss because the code often looks correct. JavaScript will accept the syntax, TypeScript may not complain, and a quick happy-path test can pass while real work still runs too late, fails silently, or happens in the wrong order.
This guide is for reviewing AI-generated JavaScript and TypeScript from tools like Cursor, Claude Code, Copilot, ChatGPT, and Codex. Use it before shipping API handlers, scripts, background jobs, checkout flows, notification systems, and batch operations.
Why Async Bugs Pass A Quick Visual Review
AI-generated JavaScript usually follows familiar shapes. It calls map, forEach, Promise.all, or an async API in a way that looks like examples from documentation. The problem is that timing is part of correctness.
A review should ask: what must finish before the function returns, what can run in parallel, what must stay sequential, and what should happen when one async operation fails?
Five Async Patterns To Check
1. async forEach is not awaited
The callback returns promises, but forEach does not wait for them. A handler can return success before emails, writes, or billing calls finish.
Risky
async function notifyUsers(users) {
users.forEach(async (user) => {
await sendEmail(user.email)
})
return { ok: true }
}Safer
async function notifyUsers(users) {
await Promise.all(users.map((user) => sendEmail(user.email)))
return { ok: true }
}2. map callback forgets to return the promise
Promise.all receives an array of undefined values, so it resolves immediately instead of waiting for the work.
Risky
async function syncOrders(orders) {
await Promise.all(orders.map((order) => {
api.syncOrder(order.id)
}))
}Safer
async function syncOrders(orders) {
await Promise.all(orders.map((order) => {
return api.syncOrder(order.id)
}))
}3. side effects continue after the HTTP response
The user sees success even if analytics, email, webhook, or database cleanup fails. That may be fine for non-critical work, but it should be intentional.
Risky
async function POST(req) {
const user = await createUser(await req.json())
sendWelcomeEmail(user.email)
return Response.json({ ok: true })
}Safer
async function POST(req) {
const user = await createUser(await req.json())
await sendWelcomeEmail(user.email)
return Response.json({ ok: true })
}4. errors are swallowed inside async handlers
Generated code sometimes catches errors only to log and continue. That can hide failed writes, partial imports, or broken payment callbacks.
Risky
async function importRows(rows) {
for (const row of rows) {
try {
await saveRow(row)
} catch (error) {
console.log(error)
}
}
}Safer
async function importRows(rows) {
const failures = []
for (const row of rows) {
try {
await saveRow(row)
} catch (error) {
failures.push({ row, error })
}
}
if (failures.length) {
throw new Error(`Failed to import ${failures.length} rows`)
}
}5. concurrency is used where order matters
Promise.all is useful, but not when every step depends on the previous step or when operations must run inside one transaction.
Risky
async function applyAccountEvents(account, events) {
await Promise.all(events.map((event) => {
return applyEvent(account.id, event)
}))
}Safer
async function applyAccountEvents(account, events) {
for (const event of events) {
await applyEvent(account.id, event)
}
}How To Review AI-Generated Async Code
- Mark every async operation that writes data, charges money, sends messages, or changes permissions.
- Decide which operations must finish before the response or success state.
- Use
Promise.allonly when independent work can safely run in parallel. - Keep sequential loops when order, rate limits, transactions, or shared state matter.
- Make failure behavior explicit: retry, return an error, store a failed job, or continue intentionally.
- Add tests for rejection, partial failure, empty arrays, duplicate requests, and slow dependencies.
Where Check AI Code Helps
Check AI Code can act as a fast first-pass review for JavaScript and TypeScript snippets. It is especially useful when AI generated a plausible API handler, webhook, script, or async workflow that you have not fully traced by hand.
It does not prove the code is correct. It can help you spot common timing and error-handling mistakes before you spend human review time on deeper product logic.
Review one async snippet
Start with the file where an AI tool wrote async control flow: checkout callbacks, email sending, imports, batch updates, or background jobs.
Try Check AI Code