Online Compiler logoOnline Compiler

JavaScript Promises: A Complete Guide

Promises are a fundamental concept in JavaScript for handling asynchronous operations. They represent a value that may be available now, or in the future, or never. Understanding promises is crucial for modern JavaScript development.

What are Promises?

A Promise is an object representing the eventual completion or failure of an asynchronous operation. It allows you to write asynchronous code that looks synchronous, making it easier to read and maintain.

Basic Promise Creation

// Creating a simple promise
const myPromise = new Promise((resolve, reject) => {
  // Asynchronous operation
  setTimeout(() => {
    const success = true;
    
    if (success) {
      resolve('Operation completed successfully!');
    } else {
      reject('Operation failed!');
...

This example shows how to create a basic promise that resolves or rejects after a timeout.

Promise States

A promise can be in one of three states:

  • Pending: Initial state, neither fulfilled nor rejected
  • Fulfilled: Operation completed successfully
  • Rejected: Operation failed

Promise States Example

const promise = new Promise((resolve, reject) => {
  console.log('Promise created - State: Pending');
  
  setTimeout(() => {
    const random = Math.random();
    if (random > 0.5) {
      resolve('Success!'); // State: Fulfilled
    } else {
      reject('Error!'); // State: Rejected
    }
...

This demonstrates how a promise transitions from pending to either fulfilled or rejected.

Promise Methods: then(), catch(), finally()

Using then(), catch(), and finally()

const fetchData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = { id: 1, name: 'John Doe' };
      resolve(data);
      // reject(new Error('Network error'));
    }, 1000);
  });
};

...

then() handles success, catch() handles errors, and finally() runs regardless of outcome.

Promise Chaining

Promises can be chained to perform sequential asynchronous operations. Each then() returns a new promise, allowing for clean sequential code.

Promise Chaining Example

const getUser = (id) => {
  return new Promise((resolve) => {
    setTimeout(() => resolve({ id, name: 'User ' + id }), 500);
  });
};

const getPosts = (userId) => {
  return new Promise((resolve) => {
    setTimeout(() => resolve(['Post 1', 'Post 2', 'Post 3']), 500);
  });
...

This shows how to chain promises to perform sequential async operations.

Promise.all() - Running Promises in Parallel

Promise.all() takes an array of promises and returns a single promise that resolves when all promises in the array have resolved, or rejects if any promise rejects.

Promise.all() Example

const promise1 = new Promise(resolve => setTimeout(() => resolve('First'), 1000));
const promise2 = new Promise(resolve => setTimeout(() => resolve('Second'), 2000));
const promise3 = new Promise(resolve => setTimeout(() => resolve('Third'), 500));

Promise.all([promise1, promise2, promise3])
  .then(results => {
    console.log('All promises resolved:', results);
    // Output: ['First', 'Second', 'Third']
  })
  .catch(error => {
...

Promise.all() waits for all promises to resolve or rejects if any fail.

Promise.race() - First to Complete

Promise.race() returns a promise that resolves or rejects as soon as one of the promises in the array resolves or rejects.

Promise.race() Example

const slowPromise = new Promise(resolve => 
  setTimeout(() => resolve('Slow'), 3000));

const fastPromise = new Promise(resolve => 
  setTimeout(() => resolve('Fast'), 1000));

const failingPromise = new Promise((resolve, reject) => 
  setTimeout(() => reject('Failed'), 500));

Promise.race([slowPromise, fastPromise])
...

Promise.race() resolves with the first promise that completes.

Promise.allSettled() - Wait for All to Complete

Promise.allSettled() waits for all promises to settle (either resolve or reject) and returns an array of objects describing each promise's outcome.

Promise.allSettled() Example

const promises = [
  Promise.resolve('Success 1'),
  Promise.reject('Error 1'),
  Promise.resolve('Success 2'),
  new Promise(resolve => setTimeout(() => resolve('Success 3'), 1000))
];

Promise.allSettled(promises)
  .then(results => {
    results.forEach((result, index) => {
...

Promise.allSettled() provides results for all promises, regardless of success or failure.

Error Handling Best Practices

Proper Error Handling

// ❌ Bad: No error handling
function riskyOperation() {
  return fetch('/api/data');
}

// ✅ Good: Proper error handling
function safeOperation() {
  return fetch('/api/data')
    .then(response => {
      if (!response.ok) {
...

Always handle errors properly in promise chains and async functions.

Converting Callbacks to Promises

Promisifying Callback-based Functions

// Original callback-based function
function readFile(path, callback) {
  // Simulate async file reading
  setTimeout(() => {
    if (Math.random() > 0.1) {
      callback(null, 'File content');
    } else {
      callback(new Error('File not found'));
    }
  }, 1000);
...

Convert callback-based functions to promise-based for better async handling.

Common Promise Patterns

Retry Pattern

function retryPromise(fn, maxRetries = 3, delay = 1000) {
  return new Promise((resolve, reject) => {
    function attempt(retriesLeft) {
      fn()
        .then(resolve)
        .catch(error => {
          if (retriesLeft > 0) {
            setTimeout(() => attempt(retriesLeft - 1), delay);
          } else {
            reject(error);
...

Implement retry logic for unreliable async operations.

Timeout Pattern

function withTimeout(promise, timeoutMs) {
  const timeoutPromise = new Promise((_, reject) => {
    setTimeout(() => reject(new Error('Operation timed out')), timeoutMs);
  });
  
  return Promise.race([promise, timeoutPromise]);
}

// Usage
const slowOperation = new Promise(resolve => 
...

Add timeout functionality to promises to prevent hanging operations.

Real-World Example: API Calls

Complete API Example with Promises

class ApiService {
  constructor(baseURL) {
    this.baseURL = baseURL;
  }

  request(endpoint, options = {}) {
    const url = `${this.baseURL}${endpoint}`;
    
    return fetch(url, {
      headers: {
...

Build a complete API service using promises for all HTTP operations.

Key Takeaways

  • Promises represent future values and help manage asynchronous operations
  • Use .then() for success handling and .catch() for errors
  • Promise.all() runs promises in parallel, Promise.race() returns the first result
  • Always handle errors to prevent uncaught promise rejections
  • Promise chaining enables sequential async operations
  • Consider async/await for cleaner syntax (covered in the next section)

Ready to learn async/await? Check out the async/await guide for modern asynchronous JavaScript.