Node.js Promises

A Promise is an object in JavaScript that represents the eventual result of an asynchronous operation. Instead of passing a callback function into every asynchronous call, a Promise allows the result to be handled later using .then() for success and .catch() for errors. Promises were introduced to solve the problem of callback hell and make asynchronous code easier to read, write, and maintain.

Think of a Promise like a receipt from a store. The order is placed (asynchronous task started), and a receipt (Promise) is received. Later, either the order arrives successfully (resolved) or there is a problem (rejected). The response is handled when the outcome is known.

Promise States

A Promise can be in one of three states:

  • Pending: The initial state — the operation has not yet completed.
  • Fulfilled (Resolved): The operation completed successfully, and the Promise has a result value.
  • Rejected: The operation failed, and the Promise has an error reason.

Once a Promise moves from pending to either fulfilled or rejected, it cannot change its state again.

Creating a Promise

const myPromise = new Promise(function(resolve, reject) {
  const success = true;

  if (success) {
    resolve("The task was completed successfully!");
  } else {
    reject(new Error("Something went wrong."));
  }
});

myPromise
  .then(function(result) {
    console.log("Success:", result);
  })
  .catch(function(err) {
    console.log("Error:", err.message);
  });

Output:

Success: The task was completed successfully!

The new Promise() constructor takes a function with two arguments: resolve (call this when the task succeeds) and reject (call this when the task fails).

Simulating Asynchronous Work with a Promise

function fetchUserData(userId) {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      if (userId > 0) {
        resolve({ id: userId, name: "Alice", role: "admin" });
      } else {
        reject(new Error("Invalid user ID"));
      }
    }, 1500); // Simulates a 1.5-second delay
  });
}

fetchUserData(1)
  .then(function(user) {
    console.log("User found:", user.name);
  })
  .catch(function(err) {
    console.log("Error:", err.message);
  });

console.log("Fetching user... (this runs immediately)");

Output:

Fetching user... (this runs immediately)
User found: Alice

Chaining Promises with .then()

One of the most powerful features of Promises is chaining. Each .then() can return a new value or a new Promise, and the next .then() receives it:

function step1() {
  return new Promise(function(resolve) {
    setTimeout(() => resolve("Step 1 done"), 500);
  });
}

function step2(result) {
  return new Promise(function(resolve) {
    setTimeout(() => resolve(result + " → Step 2 done"), 500);
  });
}

function step3(result) {
  return new Promise(function(resolve) {
    setTimeout(() => resolve(result + " → Step 3 done"), 500);
  });
}

step1()
  .then(step2)
  .then(step3)
  .then(function(finalResult) {
    console.log("Final:", finalResult);
  })
  .catch(function(err) {
    console.log("Error:", err.message);
  });

Output:

Final: Step 1 done → Step 2 done → Step 3 done

This is a clean, readable chain — a major improvement over nested callbacks.

Handling Errors with .catch()

A single .catch() at the end of a chain will catch errors from any step in the chain:

function riskyTask(shouldFail) {
  return new Promise(function(resolve, reject) {
    if (shouldFail) {
      reject(new Error("Task failed!"));
    } else {
      resolve("Task succeeded!");
    }
  });
}

riskyTask(true)
  .then(function(result) {
    console.log(result);
  })
  .catch(function(err) {
    console.log("Caught Error:", err.message);
  });

Output:

Caught Error: Task failed!

The .finally() Method

.finally() runs regardless of whether the Promise resolved or rejected. It is commonly used for cleanup operations:

fetchUserData(1)
  .then(function(user) {
    console.log("User:", user.name);
  })
  .catch(function(err) {
    console.log("Error:", err.message);
  })
  .finally(function() {
    console.log("Operation complete. Cleaning up...");
  });

Promise.all() – Running Multiple Promises Simultaneously

Promise.all() takes an array of Promises and resolves when all of them have resolved. If any one rejects, the entire thing rejects:

const promise1 = Promise.resolve("Data from API 1");
const promise2 = Promise.resolve("Data from API 2");
const promise3 = Promise.resolve("Data from API 3");

Promise.all([promise1, promise2, promise3])
  .then(function(results) {
    console.log("All results:", results);
  })
  .catch(function(err) {
    console.log("One failed:", err.message);
  });

Output:

All results: [ 'Data from API 1', 'Data from API 2', 'Data from API 3' ]

Promise.allSettled() – All Promises Regardless of Outcome

Unlike Promise.all(), Promise.allSettled() waits for all Promises to complete — even if some reject — and returns all results:

const p1 = Promise.resolve("Success");
const p2 = Promise.reject(new Error("Failure"));
const p3 = Promise.resolve("Another Success");

Promise.allSettled([p1, p2, p3])
  .then(function(results) {
    results.forEach(function(result) {
      if (result.status === 'fulfilled') {
        console.log("Fulfilled:", result.value);
      } else {
        console.log("Rejected:", result.reason.message);
      }
    });
  });

Output:

Fulfilled: Success
Rejected: Failure
Fulfilled: Another Success

Promise.race() – First One Wins

Promise.race() resolves or rejects as soon as the first Promise in the array settles:

const fast = new Promise(resolve => setTimeout(() => resolve("Fast!"), 200));
const slow = new Promise(resolve => setTimeout(() => resolve("Slow!"), 1000));

Promise.race([fast, slow])
  .then(function(result) {
    console.log("Winner:", result); // Fast!
  });

Promisifying a Callback Function

Old callback-based functions can be converted to Promise-based ones using util.promisify():

const util = require('util');
const fs = require('fs');

const readFileAsync = util.promisify(fs.readFile);

readFileAsync('notes.txt', 'utf8')
  .then(function(data) {
    console.log("File content:", data);
  })
  .catch(function(err) {
    console.log("Error:", err.message);
  });

Key Points

  • A Promise represents a value that will be available in the future — either a success or a failure.
  • Promises have three states: pending, fulfilled, and rejected.
  • Use .then() to handle success, .catch() for errors, and .finally() for cleanup.
  • Promises can be chained with .then() to sequence asynchronous operations clearly.
  • Promise.all() runs multiple Promises in parallel and waits for all to complete.
  • Promise.allSettled() waits for all Promises and returns all outcomes, even failures.
  • Promise.race() resolves/rejects with the result of whichever Promise finishes first.
  • util.promisify() converts callback-style functions into Promise-based ones.

Leave a Comment

Your email address will not be published. Required fields are marked *