Why is this async function not behaving asynchronously?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
An async function in JavaScript does not automatically make its contents run in parallel or on a background thread. The async keyword only means the function returns a Promise and allows using await inside it. If the function body contains synchronous (CPU-bound) code, that code blocks the event loop just like any other synchronous code. The most common reasons an async function appears to run synchronously are: no await on an asynchronous operation, CPU-bound work blocking the thread, awaiting Promises sequentially instead of concurrently, and calling the async function without handling the returned Promise.
The Synchronous Trap
Wrapping synchronous code in async does not move it to another thread. JavaScript is single-threaded — async only helps when you await actual asynchronous operations (network requests, timers, file I/O).
Reason 1: Missing await
Without await, fetch returns a pending Promise object instead of the resolved Response. The code continues immediately with the Promise object, not the actual data.
Reason 2: Sequential awaits Instead of Concurrent
Each await pauses execution until the Promise resolves. If the operations are independent, use Promise.all to run them concurrently.
Reason 3: CPU-Bound Work Blocking the Event Loop
async/await is for I/O-bound operations. CPU-bound work needs Web Workers (browser) or Worker Threads (Node.js) to avoid blocking.
Reason 4: Not Handling the Returned Promise
Calling an async function without await or .then() starts the operation but does not wait for it. Errors thrown inside become unhandled Promise rejections.
Reason 5: forEach Does Not Await
Array.forEach does not handle async callbacks — it calls each function but does not await the returned Promises. Use for...of or Promise.all(items.map(...)) instead.
Debugging Async Issues
Use console.time to measure whether operations run sequentially or concurrently. If the total time equals the sum of individual times, you have sequential awaits that could be parallelized.
Common Pitfalls
- Assuming async means parallel:
asyncdoes not create threads. It enables non-blocking I/O through the event loop. CPU-bound code blocks the thread regardless ofasync. - Using forEach with async callbacks:
forEachfires all callbacks but does not await them. The loop completes immediately while the async operations are still running. - Sequential awaits for independent operations: Awaiting three independent API calls sequentially takes 3x as long as running them concurrently with
Promise.all. - Forgetting to await the async function call:
asyncFn()withoutawaitreturns a Promise but does not wait for completion. Errors inside become unhandled rejections. - Wrapping synchronous code in async: Adding
asyncto a function that does only synchronous work adds Promise overhead without any benefit. Only useasyncwhen the function actually awaits something.
Summary
asyncdoes not make code run in parallel — it allowsawaitfor I/O-bound Promises- Always
awaitasynchronous operations (fetch, file reads, database queries) - Use
Promise.allfor independent operations instead of sequentialawait - Never use
forEachwith async callbacks — usefor...oforPromise.all(map(...)) - CPU-bound work needs Worker Threads, not
async/await - Always handle the Promise returned by an async function with
await,.then(), or.catch()

