JavaScript async programming helps programs handle tasks that take time, such as fetching data, reading files, or waiting for timers. This cheat sheet covers Promises, async functions, await, and common patterns for writing readable asynchronous code. Students need these tools to build responsive web apps that do not freeze while waiting for results.
It also helps make code easier to debug by showing how values and errors move through async steps.
A Promise represents a future value that may succeed or fail. The then, catch, and finally methods handle fulfilled, rejected, and cleanup cases. The async keyword makes a function return a Promise, and await pauses inside that function until a Promise settles.
For multiple independent tasks, Promise.all, Promise.allSettled, Promise.race, and Promise.any provide different ways to coordinate results.
Key Facts
- A Promise has three states: pending, fulfilled, or rejected.
- Use promise.then(value => result) to run code after a Promise fulfills.
- Use promise.catch(error => handleError) to handle a rejected Promise.
- An async function always returns a Promise, even if it returns a normal value.
- Use await only inside an async function or at the top level of a supported module.
- await pauses the async function until the Promise settles, but it does not block the whole JavaScript program.
- Use try { const data = await task(); } catch (error) { } to handle errors with async and await.
- Use Promise.all([p1, p2, p3]) when independent Promises should run in parallel and all must succeed.
Vocabulary
- Asynchronous
- Asynchronous code starts a task and lets the program continue running while waiting for the task to finish.
- Promise
- A Promise is an object that represents a value that may be available now, later, or never because of an error.
- Fulfilled
- Fulfilled means a Promise completed successfully and produced a value.
- Rejected
- Rejected means a Promise failed and produced an error or reason.
- async function
- An async function is a function declared with async that automatically returns a Promise.
- await
- await pauses execution inside an async function until a Promise is fulfilled or rejected.
Common Mistakes to Avoid
- Forgetting to await a Promise is wrong because the variable receives the Promise object instead of the resolved value.
- Using await outside an async function is wrong in many scripts because await is only valid inside async functions or supported modules.
- Writing sequential awaits for independent tasks is inefficient because each task waits for the previous one instead of running in parallel.
- Ignoring rejected Promises is wrong because errors may become unhandled and make debugging difficult.
- Thinking await blocks the entire page is wrong because await only pauses the current async function while the event loop can continue other work.
Practice Questions
- 1 What is logged by this code: async function f() { return 7; } f().then(x => console.log(x));
- 2 A function wait(ms) returns a Promise that resolves after ms milliseconds. About how long does await wait(300); await wait(500); take in total?
- 3 If p1 takes 400 ms and p2 takes 600 ms, about how long does await Promise.all([p1, p2]) take if both start at the same time?
- 4 Explain when you should use Promise.all instead of writing several await statements one after another.
Understanding JavaScript Async, Promises & Await
JavaScript runs most code on one main thread. If a slow network request held that thread still, clicks, typing, animation, and screen updates could wait behind it. Instead, the browser or runtime starts certain outside work, then JavaScript continues with other instructions.
When that work finishes, its follow-up code is placed in a queue for JavaScript to run later. Promise reactions have a special high-priority queue called the microtask queue.
This is why a Promise callback often runs before a timer callback, even when both seem ready at nearly the same moment. Knowing this order helps explain surprising console output.
A Promise chain depends on returned values. When a then callback returns an ordinary value, the next then receives that value. When it returns another Promise, the chain waits for that Promise before continuing.
Forgetting the return is a common bug. The next step may run too early with undefined, while the work that was meant to be chained continues separately. Errors move through a chain in a similar way.
An error thrown inside a then callback turns into a rejected Promise. A catch placed later can receive it. A catch that silently ignores an error can make a program appear successful when important data is missing.
Await often makes code read from top to bottom, but it is important to notice where waiting happens. If one task needs the result of another, waiting in sequence is correct. For example, an app may need a user identity before requesting that user’s saved settings.
If tasks are independent, starting both first and awaiting their results afterward avoids unnecessary delay. This matters when a page loads a profile, a list of messages, and a set of notifications.
Starting work in parallel can make the page feel faster. It should only be used when the tasks do not depend on one another and when the program can handle a failure from any task.
Real web code needs careful error decisions. A failed network connection is different from a server response that says a request was not allowed. With fetch, many HTTP error responses still produce a fulfilled Promise, so code should inspect the response status before treating the result as usable data.
User actions create another issue. A person can search several times quickly, and an older request may finish after a newer one. The page should avoid showing stale results.
AbortController can cancel a request that is no longer needed. During development, use clear error messages, keep cleanup in finally, and test slow connections, missing data, rejected permissions, and repeated clicks. These cases reveal whether asynchronous code is truly reliable.