What is Promise and why is it needed?

👨‍💻 Frontend Developer 🟡 Often Asked 🎚️ Medium
#JavaScript #Asynchronicity #JS Basics

Brief Answer

Promise is an object in JavaScript representing the result of an asynchronous operation. It can be in one of three states: pending, fulfilled, or rejected. Promises simplify working with asynchronous code, eliminating “callback hell” and enabling chains of asynchronous operations.

Key benefits:

  • Improved code readability — eliminates nested callbacks
  • Unified error handling — centralized error handling through .catch()
  • Operation chains — sequential execution of asynchronous operations
  • Compatibility — foundation for async/await

Full Answer

Promise is a fundamental concept of asynchronous programming in modern JavaScript. It represents an object that may produce a single value sometime in the future: either a resolved value or a reason why it wasn’t resolved (such as a network error).

Promise States

A Promise can be in one of three states:

  1. Pending — initial state, neither fulfilled nor rejected
  2. Fulfilled — operation completed successfully
  3. Rejected — operation failed
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
  // Asynchronous operation
  setTimeout(() => {
    const success = true;
    if (success) {
      resolve('Operation completed successfully');
    } else {
      reject('An error occurred');
    }
  }, 1000);
});
 
// Using Promise
myPromise
  .then(result => console.log(result))
  .catch(error => console.error(error));

Core Promise Methods

1. then()

Handles successful Promise resolution:

fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error));

2. catch()

Handles Promise rejection:

fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    console.error('Error loading data:', error);
    // Handle error
  });

3. finally()

Executes regardless of the result:

fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error))
  .finally(() => {
    console.log('Request completed');
    // Hide loading indicator
  });

Promise Chains

One of the main advantages of Promises is the ability to create chains:

// Chain of asynchronous operations
fetch('/api/user')
  .then(response => response.json())
  .then(user => fetch(`/api/profile/${user.id}`))
  .then(response => response.json())
  .then(profile => {
    console.log('User profile:', profile);
    return profile;
  })
  .catch(error => {
    console.error('Error in chain:', error);
  });

Static Promise Methods

1. Promise.all()

Executes multiple Promises in parallel:

const promises = [
  fetch('/api/users'),
  fetch('/api/posts'),
  fetch('/api/comments')
];
 
Promise.all(promises)
  .then(responses => Promise.all(responses.map(res => res.json())))
  .then(([users, posts, comments]) => {
    console.log('All data loaded:', { users, posts, comments });
  })
  .catch(error => {
    console.error('Error loading data:', error);
  });

2. Promise.race()

Returns the result of the first completed Promise:

const promises = [
  fetch('/api/fast-endpoint'),
  fetch('/api/slow-endpoint')
];
 
Promise.race(promises)
  .then(response => response.json())
  .then(data => {
    console.log('First response:', data);
  });

3. Promise.resolve() and Promise.reject()

Create already resolved or rejected Promises:

// Creating a resolved Promise
const resolvedPromise = Promise.resolve('Successful value');
 
// Creating a rejected Promise
const rejectedPromise = Promise.reject('Error');
 
resolvedPromise.then(value => console.log(value)); // 'Successful value'
rejectedPromise.catch(error => console.error(error)); // 'Error'

Practical Examples

Handling Multiple Asynchronous Operations

// Parallel loading of user data
async function loadUserProfile(userId) {
  try {
    const [user, posts, followers] = await Promise.all([
      fetch(`/api/users/${userId}`).then(res => res.json()),
      fetch(`/api/users/${userId}/posts`).then(res => res.json()),
      fetch(`/api/users/${userId}/followers`).then(res => res.json())
    ]);
    
    return { user, posts, followers };
  } catch (error) {
    console.error('Error loading profile:', error);
    throw error;
  }
}

Converting Callback Style to Promise

// Function with callback
function readFileCallback(filename, callback) {
  // Simulating asynchronous operation
  setTimeout(() => {
    if (filename) {
      callback(null, `File content: ${filename}`);
    } else {
      callback('File not found', null);
    }
  }, 1000);
}
 
// Converting to Promise
function readFilePromise(filename) {
  return new Promise((resolve, reject) => {
    readFileCallback(filename, (error, data) => {
      if (error) {
        reject(error);
      } else {
        resolve(data);
      }
    });
  });
}
 
// Usage
readFilePromise('example.txt')
  .then(data => console.log(data))
  .catch(error => console.error(error));

Common Mistakes and Solutions

1. Improper Error Handling

// ❌ Bad - errors in chain may be lost
fetch('/api/data')
  .then(response => response.json())
  .then(data => {
    throw new Error('Error processing data');
  })
  .then(processedData => console.log(processedData));
 
// ✅ Good - always add catch
fetch('/api/data')
  .then(response => response.json())
  .then(data => {
    throw new Error('Error processing data');
  })
  .then(processedData => console.log(processedData))
  .catch(error => console.error('Error:', error));

2. Forgotten Return in Chain

// ❌ Bad - value lost in chain
fetch('/api/user')
  .then(response => response.json())
  .then(user => {
    // Forgot return
    fetch(`/api/profile/${user.id}`).then(res => res.json());
  })
  .then(profile => {
    // profile will be undefined
    console.log(profile);
  });
 
// ✅ Good - return Promise
fetch('/api/user')
  .then(response => response.json())
  .then(user => {
    return fetch(`/api/profile/${user.id}`).then(res => res.json());
  })
  .then(profile => {
    console.log(profile);
  });

Best Practices

  1. Always handle errors — use .catch() or try/catch with async/await
  2. Use Promise.all() for parallel execution of independent operations
  3. Avoid nesting — create chains instead of nested .then()
  4. Return Promises from functions — to maintain chain
  5. Use async/await — for more readable code when working with Promises

Key Promise Benefits

  1. Improved readability — eliminates “callback hell”
  2. Unified error handling — centralized system through .catch()
  3. Composition — easy combination of multiple asynchronous operations
  4. Modern standard — foundation for async/await and other asynchronous patterns

Promise is a powerful tool for working with asynchronous code in JavaScript. Understanding Promises allows you to create more readable, maintainable, and reliable code when working with network requests, timers, and other asynchronous operations.


Want more articles to prepare for interviews? Subscribe to EasyAdvice, bookmark the site and improve yourself every day 💪