← thecodex.expert · The Codex Family of Knowledge
The Codex / JavaScript / Async & Promises

Async & Promises

Callbacks, Promises, async/await, and fetch — handling things that take time in JavaScript.

What it is, in plain English: Asynchronous code is code that starts running now but finishes later — fetching data from a server, reading a file, waiting for a timer. JavaScript handles this with Promises: an object that represents a value that isn't ready yet. async/await is the modern syntax that makes Promises look and feel like regular sequential code, while still being non-blocking. Understanding async is the gateway to real-world JavaScript.
Where this bites you

Each .then() passes on whatever its callback returns. An arrow function with braces returns nothing unless you write return, so the next step in the chain receives undefined. It also doesn't wait: the inner async work still runs, but nothing is connected to it anymore.

const getCity = () => new Promise(r => setTimeout(() => r("Mumbai"), 50));
const getForecast = (city) => new Promise(r => setTimeout(() => r(`${city}: 28°C`), 50));

getCity()
  .then(city => {
    getForecast(city);          // braces: no automatic return
  })
  .then(forecast => console.log("forecast:", forecast));
// forecast: undefined

Code that waits — and why JavaScript needs it

Imagine you're building a weather app. When the user opens it, you need to fetch the current temperature from a server. That takes time — maybe half a second. If JavaScript stopped everything and waited, the entire page would freeze. Buttons wouldn't work. Nothing would respond. That's not acceptable.

JavaScript's answer: start the fetch, keep doing other things, and when the data arrives, handle it. This is asynchronous programming.

The old way: callbacks

// A callback is a function you pass to another function, to be called "when ready"
setTimeout(function() {
  console.log("This runs after 1 second");
}, 1000);

console.log("This runs immediately");
// Output:
// "This runs immediately"
// "This runs after 1 second"  ← one second later

Callbacks work, but they get messy fast — "callback hell" happens when you need multiple async operations in sequence:

// Callback hell — hard to read, hard to maintain:
getUser(1, function(user) {
  getPosts(user.id, function(posts) {
    getComments(posts[0].id, function(comments) {
      // now we can actually do something...
    });
  });
});

Promises — a cleaner approach

A Promise is an object that represents a value that will be available in the future. It's either pending (not done yet), fulfilled (succeeded), or rejected (failed).

// A function that returns a Promise:
function getWeather(city) {
  return new Promise((resolve, reject) => {
    // Simulate a network request (takes 500ms):
    setTimeout(() => {
      if (city === "Unknown") {
        reject(new Error(`City "${city}" not found`));
      } else {
        resolve({ city, temp: 28, condition: "Sunny" });
      }
    }, 500);
  });
}

// Use .then() to handle success, .catch() for errors:
getWeather("Mumbai")
  .then(data => {
    console.log(`${data.city}: ${data.temp}°C, ${data.condition}`);
  })
  .catch(error => {
    console.error("Error:", error.message);
  });

Promise chains work by returning: when a .then callback returns a Promise, the next .then waits for it and receives its result. Add the return (or drop the braces, so the arrow returns automatically). Writing the same flow with async/await, below, avoids this class of mistake entirely:

getCity()
  .then(city => {
    return getForecast(city);   // the next .then waits for this Promise
  })
  .then(forecast => console.log("forecast:", forecast));
// forecast: Mumbai: 28°C

async/await — Promises made readable

async/await is syntactic sugar over Promises. It lets you write async code that reads like synchronous code — no chains of .then().

// Mark the function as async:
async function showWeather() {
  try {
    // 'await' pauses this function until the Promise resolves:
    const weather = await getWeather("Mumbai");
    console.log(`${weather.city}: ${weather.temp}°C`);

    // You can await multiple things in sequence:
    const weather2 = await getWeather("Delhi");
    console.log(`${weather2.city}: ${weather2.temp}°C`);

  } catch (error) {
    // If any await throws, execution jumps here:
    console.error("Failed:", error.message);
  }
}

showWeather();

// Important: the code AFTER calling an async function runs immediately:
console.log("Getting weather..."); // this prints FIRST
Key insight: await only pauses the function it's in, not the whole program. JavaScript keeps running other code while waiting. That's what makes it non-blocking.

fetch — the most common real-world async operation

// fetch() returns a Promise — use await with it:
async function getUser(id) {
  try {
    const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);

    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }

    const user = await response.json();  // also a Promise!
    console.log(`Name: ${user.name}`);
    console.log(`Email: ${user.email}`);

  } catch (error) {
    console.error("Request failed:", error.message);
  }
}

getUser(1);

Note: fetch() works in browsers. In Node.js it's built-in from v18+. The Try It editor above runs in the browser, so fetch to external URLs works there.

Running multiple Promises in parallel

async function getMultipleUsers() {
  // Sequential — slow (waits for each before starting next):
  const user1 = await getWeather("Mumbai");
  const user2 = await getWeather("Delhi");   // starts after Mumbai finishes

  // Parallel — fast (all start at the same time):
  const [mumbai, delhi] = await Promise.all([
    getWeather("Mumbai"),
    getWeather("Delhi")
  ]);
  console.log(mumbai.city, delhi.city);  // both arrive together
}
Try It Yourself

Official Sources

The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.