RMRM Full Stack & AI Engineer · All guides · Roadmaps
Languages · guide

async/await Explained

async/await is a modern JavaScript syntax built on top of Promises that lets you write asynchronous code in a style that looks and reads like synchronous code. It dramatically reduces callback nesting and makes error handling straightforward.

What Is async/await?

The async keyword turns a regular function into one that always returns a Promise. The await keyword can only be used inside an async function and pauses execution of that function until the awaited Promise resolves or rejects. This pausing is non-blocking — the JavaScript event loop continues processing other tasks while waiting.

Why It Matters

Before async/await, chaining asynchronous operations required deeply nested callbacks or long .then() chains, both of which are hard to read and debug. async/await flattens that structure into linear, top-to-bottom code that closely resembles synchronous logic. This reduces cognitive overhead and makes intent far clearer to other developers.

How It Works Under the Hood

An async function is syntactic sugar over the Promise API — the JavaScript engine desugars it into a state machine that resumes after each awaited Promise settles. When you await a Promise, the function's execution context is suspended and pushed off the call stack, freeing the event loop. Once the Promise settles, the engine reschedules the function to continue from where it left off.

Error Handling

Rejected Promises surfaced by await will throw inside the async function, so you can catch them with a standard try/catch block instead of a .catch() chain. Unhandled rejections in async functions still propagate as unhandled Promise rejections, so always wrap awaited calls that can fail. A common pattern is a single try/catch around a whole block of awaited operations when you want unified error handling.

Common Gotcha: Unnecessary Serialization

A frequent mistake is awaiting independent Promises one after another, which forces them to run sequentially even though they could run in parallel. For example, awaiting two API calls back-to-back doubles the wait time unnecessarily. Use Promise.all() to await multiple independent Promises concurrently: const [a, b] = await Promise.all([fetchA(), fetchB()]).

Best Practice: Avoid Forgetting await

Omitting await before a Promise-returning function call means you receive a pending Promise object instead of the resolved value — a silent bug that is easy to miss. Linters like ESLint with the no-floating-promises rule can catch these omissions automatically. Always ensure every Promise you care about is either awaited, returned, or explicitly handled with .catch().

Go deeper with an AI tutor that teaches this in context — and quizzes you on it.
Open the app — free to start

© RM Full Stack & AI Engineer · All guides · Roadmaps · Open the app