JS CodeLab

JavaScript Promises & Async/Await Guide (with Examples)

By JS CodeLab Engineering Team•Last updated: October 12, 2026•5 min read

Direct Answer & Key Concept

A JavaScript Promise represents the eventual completion or failure of an asynchronous operation and its resulting value. Async/await provides synchronous-like syntax for handling Promises without callback hell.

Runnable Code SnippetRun in Playground
// Practical Async/Await with Error Handling
function simulateApiCall(success) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (success) resolve({ status: 200, data: "Payload retrieved" });
      else reject(new Error("Network timeout"));
    }, 100);
  });
}

async function runDemo() {
  try {
    const result = await simulateApiCall(true);
    console.log("Success:", result.data);
  } catch (err) {
    console.error("Error caught:", err.message);
  }
}

runDemo(); // Success: Payload retrieved

What is asynchronous programming in JavaScript?

JavaScript operates on a single-threaded event loop, meaning it processes one instruction at a time on its main thread. Asynchronous programming allows operations that take unpredictable time—such as fetching API data, querying databases, reading files, or setting timers—to run in the background without freezing the browser interface or blocking subsequent code execution.


What are the Three States of a Promise?

A Promise acts as a proxy for a value that is not necessarily known when the promise is created. Every Promise exists in one of three mutually exclusive states:

  1. Pending: Initial state; the asynchronous operation is still executing.
  2. Fulfilled: The operation completed successfully, yielding a resolved result value.
  3. Rejected: The operation failed or encountered an unhandled exception, producing an error reason.

Once a Promise transitions from pending to either fulfilled or rejected, it is considered settled and its state becomes permanently immutable.


How does async/await improve on Promises?

Introduced in ECMAScript 2017 (ES8), the async and await keywords are syntactic sugar built directly on top of native JavaScript Promises. They allow asynchronous code to be structured, read, and maintained with the linear clarity of synchronous code.

| Feature | Standard Promise (.then / .catch) | Modern async / await | | :--- | :--- | :--- | | Syntax Style | Method chaining with callback closures | Linear, synchronous appearance | | Error Handling | .catch(err => ...) chain | Standard try / catch blocks | | Conditional Flow | Requires nested then branches | Standard if / else statements | | Return Type | Always returns a Promise | Async function always returns a Promise |

Writing Clean Async/Await Pipelines

async function fetchUserProfile(userId) {
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const user = await response.json();
    return user;
  } catch (error) {
    console.error("Failed to load user profile:", error.message);
    throw error;
  }
}

Promise Combinators: all vs allSettled vs race vs any

When managing multiple concurrent asynchronous requests, ECMAScript provides four distinct static combinators:

  • Promise.all([p1, p2]): Fulfills when all promises fulfill; rejects immediately if any single promise rejects (fail-fast).
  • Promise.allSettled([p1, p2]): Waits until all promises settle, returning an array of objects detailing whether each succeeded or failed.
  • Promise.race([p1, p2]): Settles as soon as the first promise settles (either fulfills or rejects).
  • Promise.any([p1, p2]): Fulfills as soon as the first promise fulfills; rejects only if all promises reject.

Common Mistakes with Asynchronous JavaScript

  1. Forgetting to await a Promise: Forgetting await assigns the Promise object itself rather than its resolved value, causing code like if (fetchData()) to evaluate as truthy regardless of the result.
  2. Sequential execution of independent operations: Running await fetchA(); await fetchB(); runs network requests in series. If they are independent, run them concurrently with await Promise.all([fetchA(), fetchB()]).
  3. Unhandled promise rejections: Forgetting to attach a .catch() or omit try/catch can crash server processes in Node.js and trigger unhandled rejection warnings in modern browsers.

Frequently Asked Questions About JavaScript Promises & Async/Await (with Examples)

What does an async function always return in JavaScript?

An async function always implicitly returns a Promise. If the function returns a non-promise value, JavaScript automatically wraps that value in a resolved Promise (Promise.resolve(value)).

What is the difference between Promise.all and Promise.allSettled?

Promise.all rejects immediately if any input promise rejects (fail-fast), whereas Promise.allSettled waits for every promise to either resolve or reject, returning an array of status objects.

How do you handle errors in async/await functions?

Wrap the awaited asynchronous operations in a standard try/catch block. Any rejection or thrown error within the try block is routed directly into the catch block.

Can you use await outside of an async function?

In modern JavaScript (ES2022+), Top-Level Await is supported at the root level of JavaScript modules (.mjs or type='module'), but inside standard function bodies, await requires the enclosing function to be declared async.

Related Runnable JavaScript Examples

Ready to test what you learned?

The best way to solidify your JavaScript skills is by writing and running code. Open our free online compiler and test your scripts with real-time console feedback.

Open JavaScript Compiler