async/await function does not wait for setTimeout to finish
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
await only works with Promises, but setTimeout does not return a Promise β it returns a numeric timer ID. Writing await setTimeout(fn, 1000) does not pause execution for 1 second. The await resolves immediately with the timer ID, and the callback runs later. The fix is to wrap setTimeout in a Promise so that await can properly wait for the delay to complete.
The Problem
await expects a Promise (a thenable). setTimeout returns a number (the timer ID). When you await a non-Promise value, JavaScript wraps it in Promise.resolve(timerId), which resolves instantly.
The Fix: Wrap setTimeout in a Promise
The delay function creates a Promise that resolves when setTimeout fires. Now await has a real Promise to wait on.
Using Node.js Built-In timers/promises
Node.js 16+ provides a Promise-based setTimeout out of the box:
This is the cleanest approach in Node.js β no need to write your own wrapper.
Running Code After the Delay
If you need to run logic after the delay:
Cancellable Delay
Sometimes you need to cancel a pending delay:
With AbortController (Node.js timers/promises):
Why setInterval Has the Same Issue
Common Pitfalls
- Assuming
awaitpauses any asynchronous function:awaitonly pauses on Promises. Callback-based APIs likesetTimeout,setInterval,fs.readFile(callback version), and event listeners do not return Promises, soawaithas no effect on them. - Wrapping the callback, not the delay: Writing
await new Promise(resolve => setTimeout(() => { doStuff(); resolve(); }, 1000))works but is verbose. Separate the delay from the logic βawait delay(1000); doStuff();is cleaner. - Forgetting that
setTimeout(fn, 0)is not instant:setTimeout(fn, 0)still defersfnto the next iteration of the event loop. Wrapping it in a Promise and awaiting it introduces a microtask + macrotask boundary, which can affect ordering with other Promises. - Using
awaitin aforEachcallback:Array.forEachignores the return value (and thus the Promise) fromasynccallbacks. Usefor...oforPromise.all(arr.map(...))instead for sequential or parallel async operations. - Not handling errors in delayed operations: If the code after
await delay(ms)throws, the error propagates as an unhandled Promise rejection unless the caller usestry/catchor.catch().
Summary
setTimeoutreturns a timer ID, not a Promise βawaitdoes not wait for it- Wrap
setTimeoutin a Promise:new Promise(resolve => setTimeout(resolve, ms)) - In Node.js 16+, use
import { setTimeout } from 'timers/promises'for a built-in Promise-based delay - Use
AbortControllerwithtimers/promisesfor cancellable delays - Replace
setIntervalwithforloops andawait delay()for async-friendly polling - Only
awaitvalues that are Promises β callback APIs require wrapping first

