JS: Promise Tutorial

By Xah Lee. Date: . Last updated: .

(new in ECMAScript 2015)

xtodo

A Promise is an object that is used as a placeholder for the eventual results of a deferred (and possibly asynchronous) computation.

new Promise(executorF)

executorF should be a function that takes 2 args, fResolve, fReject. Both are also functions.

const execF = (fResolve, fReject) => {
setTimeout(() => {
fResolve("fResolve called");
  }, 999);
};

const pms = new Promise(execF);

pms.then((x) => {
// x is whatever we passed in the fResolve() function
  console.log(x);
});
// fResolve called
// define a asynchronous function using Promise object

const pms = new Promise((f1, f2) => {
f1("f1 called");
});

// call it
pms.then((x) => console.log(x));

// prints "f1 called"

States of Promise

A Promise object p is in one of three mutually exclusive states: fulfilled, rejected, and pending:

p is settled if it is not pending.


Properties

Promise.all

Promise.race

Promise.reject

Promise.resolve

Promise.prototype

Promise.prototype.constructor

Promise.prototype.catch

Promise.prototype.then

xtodo

JavaScript Promises Tutorial (ai generated)

JavaScript Promises Tutorial

Promises are one of the most important features in modern JavaScript for handling asynchronous code. They represent the eventual completion (or failure) of an async operation and its resulting value.

Why Promises?

Before Promises, we used callbacks. This led to "callback hell":

getData(function(result) {
  process(result, function(processed) {
    save(processed, function() {
      console.log("Done");
    });
  });
});

Promises make async code more readable, chainable, and easier to handle errors.


What is a Promise?

A Promise is an object that has three states:

Once a promise is fulfilled or rejected, it stays in that state (immutable).


Creating a Promise

const myPromise = new Promise((resolve, reject) => {
  // async operation here
  const success = true;

  setTimeout(() => {
    if (success) {
      resolve("Operation succeeded!");   // fulfill
    } else {
      reject(new Error("Operation failed!")); // reject
    }
  }, 1000);
});

Consuming a Promise

Use .then(), .catch(), and .finally():

myPromise
  .then((result) => {
    console.log("Success:", result);
  })
  .catch((error) => {
    console.error("Error:", error.message);
  })
  .finally(() => {
    console.log("This runs no matter what");
  });

You can also use the modern async/await syntax (recommended for most code):

async function run() {
  try {
    const result = await myPromise;
    console.log("Success:", result);
  } catch (error) {
    console.error("Error:", error.message);
  } finally {
    console.log("Cleanup");
  }
}

run();

Chaining Promises

This is where Promises shine:

function fetchUser(id) {
  return new Promise(resolve => {
    setTimeout(() => resolve({ id, name: "Xah" }), 500);
  });
}

function fetchPosts(userId) {
  return new Promise(resolve => {
    setTimeout(() => resolve(["Post1", "Post2"]), 500);
  });
}

fetchUser(1)
  .then(user => {
    console.log("User:", user);
    return fetchPosts(user.id);   // return another promise
  })
  .then(posts => {
    console.log("Posts:", posts);
  })
  .catch(err => console.error(err));

Each .then() receives the resolved value from the previous promise.


Promise Static Methods

Method Description Use Case
Promise.all() Wait for all promises to resolve Multiple parallel requests
Promise.allSettled() Wait for all to settle (fulfilled or rejected) When you want all results
Promise.race() First promise to settle wins Timeout / fastest response
Promise.any() First promise to fulfill First successful response
Promise.resolve() Create already resolved promise Quick values
Promise.reject() Create already rejected promise Quick errors

Examples

// 1. Promise.all
const promises = [
  fetchUser(1),
  fetchPosts(1),
  Promise.resolve("static")
];

Promise.all(promises)
  .then(results => console.log(results))
  .catch(err => console.error("One failed:", err));

// 2. Promise.race with timeout
function timeout(ms) {
  return new Promise((_, reject) => setTimeout(() => reject("Timeout"), ms));
}

Promise.race([fetchUser(1), timeout(100)])
  .then(user => console.log("Got user fast"))
  .catch(err => console.log(err));

Error Handling Best Practices

  1. Always use .catch() or try/catch with await
  2. Reject with real Error objects (not strings)
  3. Use .finally() for cleanup
  4. Avoid mixing callbacks and promises

Common pattern:

async function safeFetch(url) {
  try {
    const res = await fetch(url);
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (err) {
    console.error("Fetch failed:", err);
    throw err; // re-throw if caller needs to handle
  }
}

Converting Callbacks to Promises

// Old callback style
function oldAPI(callback) {
  setTimeout(() => callback(null, "data"), 1000);
}

// Modern
const promiseAPI = () =>
  new Promise((resolve, reject) => {
    oldAPI((err, data) => {
      if (err) reject(err);
      else resolve(data);
    });
  });

Many libraries already provide promise versions (fs.promises, util.promisify()).


Real-World Example: Fetch API

async function getUserData(username) {
  try {
    const response = await fetch(`https://api.github.com/users/${username}`);
    if (!response.ok) throw new Error("User not found");

    const user = await response.json();
    console.log(user);
    return user;
  } catch (error) {
    console.error("Failed to fetch user:", error);
  }
}

getUserData("xah_lee");

Key Rules to Remember


Practice Exercises

  1. Create a promise that resolves after 2 seconds with your name.
  2. Chain 3 promises that each add 1 to a number.
  3. Use Promise.all to load 3 different fake API calls.
  4. Implement a timeout wrapper for any promise.

Would you like me to expand any section (e.g., more advanced patterns, microtasks, or a full project example with loading states)? Just ask!