Promises

Promises

A complete explanation for promises in JavaScript

What are promises?

Promises in javascript represents the eventual completion (or failure) of an asynchronous operation & its resulting value.

Explanation

In real life, There are a lot of things which require a lot of time which you usually outsource like baking a cake, getting your altered etc. If you need a cake for your birthday or for your cousin's birthday, let's say. You could bake that cake yourself or you could go to a bakery.

If you go to a bakery, You ask a baker to bake a cake. The baker promises you to bake a cake for you in a given time frame, let's say in a day or two. So, The baker is basically giving you a promise to bake the cake for you. If you like, You could stay there for the whole day or carry on with another things which require your attention.

In similar way, There are some tasks which require more time to complete and because of that, we don't want our program to come to halt waiting for that task to be completed. This is where promises come into play.

Usage

Javascript promises are best ways to deal with asynchronous operations. Now, Callbacks could also be used for handling asynchronous operations.

Syntax

const promise = new Promise((resolve, reject) => {
    if(success){
        resolve('Success'); // For successful cases
    }
    else{
        reject('Failure'); // For Failure cases
    }
};

Let's understand the difference through a use-case which is to be implemented using both promises and callbacks.

Use case

  • Check the user authentication status.

  • Fetch the shopping cart items.

  • Make payments based on cart items.

Implementation using callbacks

// Simulating user authentication with callbacks
function authenticateUserWithCallback(username, password, callback) {
  setTimeout(() => {
    if (username === 'john' && password === 'secret') {
      callback(null, { userId: 123, username: 'john' });
    } else {
      callback(new Error('Authentication failed'), null);
    }
  }, 1000);
}

// Simulating fetching shopping cart with callbacks
function fetchShoppingCartWithCallback(userId, callback) {
  setTimeout(() => {
    const cartItems = ['item1', 'item2', 'item3'];
    callback(null, cartItems);
  }, 1000);
}

// Simulating making a payment with callbacks
function makePaymentWithCallback(items, callback) {
  setTimeout(() => {
    const totalAmount = items.length * 10;
    callback(null, totalAmount);
  }, 1000);
}

// Example usage with callbacks
authenticateUserWithCallback('john', 'secret', (authError, user) => {
  if (authError) {
    console.error('Authentication failed:', authError.message);
  } else {
    fetchShoppingCartWithCallback(user.userId, (cartError, cartItems) => {
      if (cartError) {
        console.error('Error fetching shopping cart:', cartError.message);
      } else {
        makePaymentWithCallback(cartItems, (paymentError, totalAmount) => {
          if (paymentError) {
            console.error('Payment failed:', paymentError.message);
          } else {
            console.log('Payment successful. Total amount:', totalAmount);
          }
        });
      }
    });
  }
});

As in the above example, You can see that the call backs are nested inside the each other. As we add another use case for let's say adding the item to the cart or any other feature. Then, a another function would be defined and another call back function would be added to the tree.

The above condition is known as 'Pyramid of doom' or 'Callback hell'. As the features will grow so will this callback tree and soon it will become unmanageable.

Here comes the promises to solve this scenario. Let's see the implementation of same use case using promises.

Implementation using promises

// Simulating user authentication with promises
function authenticateUserWithPromise(username, password) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (username === 'john' && password === 'secret') {
        resolve({ userId: 123, username: 'john' });
      } else {
        reject(new Error('Authentication failed'));
      }
    }, 1000);
  });
}

// Simulating fetching shopping cart with promises
function fetchShoppingCartWithPromise(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const cartItems = ['item1', 'item2', 'item3'];
      resolve(cartItems);
    }, 1000);
  });
}

// Simulating making a payment with promises
function makePaymentWithPromise(items) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const totalAmount = items.length * 10;
      resolve(totalAmount);
    }, 1000);
  });
}

// Example usage with promises
authenticateUserWithPromise('john', 'secret')
  .then((user) => fetchShoppingCartWithPromise(user.userId))
  .then((cartItems) => makePaymentWithPromise(cartItems))
  .then((totalAmount) => console.log('Payment successful. Total amount:', totalAmount))
  .catch((error) => console.error('Error:', error.message));

As you can see in the above example, The growing features could be handled by chaining then method back-to-back for successful cases and using catch method for failure cases.

Thank you for reading!!!!!!!!!