Async JavaScript: Promises, Fetch, and Timers
Asynchronous JavaScript lets your program keep working while it waits for a network response, a file, or a timer. Callbacks, promises, and async/await let you handle the result when it is ready.
This builds on the functions and objects in the JavaScript guide.
A spinner that never stops, a form that submits twice, or a search that shows the previous result usually comes down to when the code runs and which result it uses.
Which JavaScript Async Topic Should You Learn First?
Start with promises and async/await, then use fetch to request data. Learn timer ordering and race conditions when several operations can overlap.
- Promises and async/await covers what a promise is, the
thenandcatchpair, theasyncandawaitkeywords that usually stand in for athenchain in everyday code, overlapping work withPromise.all, and where a thrown error ends up. - Fetch, JSON, and the Network covers requesting data over HTTP, checking
response.okbefore reading the body, turning a response into an object, sending data with POST, and canceling a request that is taking too long. - Timers, Ordering, and Race Conditions covers
setTimeoutandsetInterval, the order things print, the stale response problem that breaks search boxes, and the debounce and throttle patterns that stop a handler firing on every keystroke.
How Does Asynchronous JavaScript Run?
On a single JavaScript thread, one task runs at a time. The current synchronous code finishes before a queued callback runs. When something slow starts, the API you called usually hands back a placeholder, your code carries on, and the engine returns to your callback once there is a result.
Not every API is shaped that way. Some return a promise, some take a callback, and some hand back a handle or nothing at all. The pattern below is the one they all share.
console.log("1: asking for the data");
setTimeout(() => console.log("3: the data arrived"), 0);
console.log("2: carrying on"); // node output:
// 1: asking for the data
// 2: carrying on
// 3: the data arrived The delay was zero and the third line still printed last. That is not a timing accident. Work handed off this way is queued, and the queue is only read once the currently running piece of code has finished and given the engine back.
Every subject in this topic follows from that one behavior. Promises exist to give you a handle on a value that has not arrived. Race conditions exist because two of those handles can come back in an order you did not choose.
Four Requests, One Wait
Independent requests can overlap. Starting four requests together can reduce the total wait compared with starting each one only after the previous request finishes.
// urls holds four endpoints; getJson wraps fetch and returns
// the parsed body. Each request takes roughly 300ms.
// sequential: each await holds the next request back
const sequential = [];
for (const url of urls) {
sequential.push(await getJson(url));
}
// overlapping: all four start, then you wait once
const overlapping = await Promise.all(urls.map(getJson)); // Illustrative timings, not a benchmark:
// sequential: 1218ms
// overlapping: 312ms When all four requests fulfill, both produce the same values. The loop asks for the second URL only once the first has come back, so its total is roughly the four waits added together.
The second version splits the job in two. urls.map(getJson) starts all four requests, and Promise.all watches those promises and waits for them, so the total is roughly the slowest single request.
Choosing between them is a question about dependency rather than speed. If the second request needs a value from the first, the loop is correct and the delay is real work. If the four calls know nothing about each other, the loop is only spending time.
One detail worth carrying into the guides: Promise.all rejects as soon as any input rejects, and the requests already in flight are not canceled by that. When you want every outcome regardless of failures, Promise.allSettled waits for all of them and reports each result separately.
The promises guide works through both shapes with error handling attached, and the timers and races guide covers what happens when results come back in an order you did not plan for.
Telling an Async Bug from an Ordinary One
To investigate an async bug, check whether the failure depends on timing, response order, or a shared value that changes between operations.
First, does the wrong behavior survive when the slow part is removed? Swap the request for a value returned immediately, and a bug that stays put suggests the event handling or the state rather than the waiting.
Second, does the output order change between runs? Varying output can indicate an ordering problem, though plenty of async bugs are perfectly repeatable and plenty of ordinary ones are not.
Third, does it only show up on a slow connection or a tired machine? Throttling the network in your browser tools may expose a race that a fast local server was hiding.
These checks narrow the search, but they do not identify the bug by themselves. A missing await, an unchecked HTTP error, and an outdated response can all leave the page showing the wrong data.
Common Pitfalls
- Assuming HTTP errors reject fetch:
fetchresolves happily on a 404 or a 500, so code without anresponse.okcheck reads a server error page as data. The fetch guide covers the two failure families and how to tell them apart. - Forgetting
awaitand wrapping the call intry: the function returns before anything fails, thecatchblock never runs, and the failure surfaces later, somewhere you were not looking. What the host does with it then varies. The promises guide shows exactly what that looks like. - Awaiting inside a loop when the calls do not depend on each other: four independent requests take roughly the sum of their durations rather than the longest of them.
Promise.allis the fix, and the same guide covers when sequential really is correct. - Rendering whatever comes back last: a slow earlier request can land after a fast later one and overwrite it. The timers and races guide covers the two guards worth knowing.
- Leaving an interval running:
setIntervalkeeps firing until something callsclearInterval, whether or not the thing that started it is still on screen. The same guide covers cleanup.
Frequently Asked Questions
Which JavaScript async guide should beginners read first?
Beginners should start with JavaScript promises and async/await, then learn fetch and JSON. The timers and race-conditions guide explains what happens when callbacks and requests overlap.
Why can asynchronous JavaScript be harder to debug?
Asynchronous JavaScript can be harder to debug because operations may finish in a different order from the one in which they started. Check when each operation starts, finishes, and updates shared data.
Does asynchronous JavaScript run code in parallel?
Asynchronous JavaScript does not automatically run JavaScript code in parallel. It lets operations overlap while a thread waits for results. Web Workers or Node worker threads can run JavaScript on separate threads.
Do JavaScript callbacks still matter when you use await?
JavaScript callbacks still matter when you use await. Event listeners and timers take callback functions, while await handles values returned by promises. The two approaches often appear in the same feature.
Does every JavaScript async function need try/catch?
A JavaScript async function does not need its own try/catch if its caller handles the rejection. Catch an error where you can recover, report it, or add useful context, and make sure every rejected promise is handled somewhere.
Where can you run these JavaScript async examples?
Most of these JavaScript async examples run in Node. Save snippets with top-level await as .mjs files, or use a package.json containing { "type": "module" }. Examples that access page elements need a browser instead.
Sources
-
[1]
Using Promises(developer.mozilla.org)
-
[2]
Asynchronous JavaScript(developer.mozilla.org)
-
[3]
Fetch API(developer.mozilla.org)
Read Next
What a promise is and the three states it can be in, then and catch, the async and await keywords, waiting on overlapping work with Promise.all, and where a thrown error ends up.
Requesting data with fetch, the response.ok check most tutorials skip, parsing JSON, sending data with POST, telling a bad answer from no usable answer, and timeouts with AbortController.
Is JavaScript still worth learning with AI agents? What it is used for beyond the browser, JavaScript or Python first, frontend or backend, Node, Deno or Bun, and a roadmap to every guide.