8-ES6++: Promises

hassanzohdy

Hasan Zohdy

Posted on November 19, 2022

8-ES6++: Promises

Promises In JavaScript

In ES6 we have a new way to work with asynchronous code, which is called promises. Promises are a new way to handle asynchronous code in JavaScript. They are a new way to write asynchronous code that is easier to read and write.

If you don't know what is asynchronous code, let me tell you what is it.

Asynchronous code is code that doesn't run in order. For example, if you have a function that makes an HTTP request to get some data, that function will take some time to get the data, so the code after that function will run before the function finishes, that is asynchronous code.

Usually it is handled using callbacks, but promises are a new way to handle asynchronous code.

If you are not familiar with callbacks, let me tell you what is it.

Callbacks are functions that are passed to another function as a parameter, and that function will call the callback function when it finishes, in our previous example we'll pass a callback function to the function that makes the HTTP request, and that function will call the callback function when it finishes.

Now back to our topic, promises.

Promises are defined with new Promise(), and it takes a function as a parameter, that function takes two parameters, resolve and reject, and we use them to resolve or reject the promise.

Let's see an example:

const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Hello World");
  }, 1000);
});

promise.then((data) => {
  console.log(data); // Hello World
});
Enter fullscreen mode Exit fullscreen mode

So what does resolve do? It resolves the promise A.K.A it marks it as completed successfully, and what does reject do? It rejects the promise A.K.A it marks it as failed.

In the example above, we defined a promise that will resolve after 1 second, and then we used then to get the data from the promise.

We can also use catch to handle errors, where the failed promise will be handled.


const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject("Error");
  }, 1000);
});

promise.catch((error) => {
  console.log(error); // Error
});
Enter fullscreen mode Exit fullscreen mode

So we can conclude that the promise receives a callback function which receives two parameters, resolve and reject, and the promise object is returned with couple of methods (so far), the then method to handle the resolved success promise, and the catch method to handle the rejected failed promise.

Chaining Promises

We can chain promises, and that is very useful when we have multiple asynchronous operations that depend on each other.

For example, Let's say we have a function to get the user data, and another function to get the user posts, and we want to get the user data first, then get the user posts, and then log the user data and the user posts.

const getUserData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        id: 155,
        name: "Hasan",
        age: 20,
      });
    }, 1000);
  });
};

const getUserPosts = (userId) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(["Post 1", "Post 2", "Post 3"]);
    }, 1000);
  });
};

getUserData()
  .then((user) {
    getUserPosts(user.id).then(posts => {
      console.log(user, posts);
    });
  });
Enter fullscreen mode Exit fullscreen mode

So we can see that we have two promises, the first one is to get the user data, and the second one is to get the user posts, and we want to get the user data first, then get the user posts, and then log the user data and the user posts.

In brief, we can use then to get the user data, and then use then again to get the user posts, and then log the user data and the user posts.

But there is a problem, what if we have more than two promises, and we want to get the user data first, then get the user posts, then get the user friends, and then get the user friends posts, and then log the user data and the user posts and the user friends and the user friends posts?

Here comes the magical solution, the async and await operators.

Async and Await

The async and await operators are used to handle promises in a more readable way, and they are used with promises.

How they work? Let's see an example:

const getUserData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        id: 155,
        name: "Hasan",
        age: 20,
      });
    }, 1000);
  });
};

const getUserPosts = (userId) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(["Post 1", "Post 2", "Post 3"]);
    }, 1000);
  });
};

const getUserDataAndPosts = async () => {
  const user = await getUserData();
  const posts = await getUserPosts(user.id);
  console.log(user, posts);
};

getUserDataAndPosts();
Enter fullscreen mode Exit fullscreen mode

So we can see that we have two promises, the first one is to get the user data, and the second one is to get the user posts, and we want to get the user data first, then get the user posts, and then log the user data and the user posts.

Let's split the code into two parts, the first part is the async function, and the second part is the await operators.

The code before getUserDataAndPosts is the same as the previous example, so we'll cover starting from const getUserDataAndPosts = async () => {.

The async keyword is used to define an async function, and the await keyword is used to wait for a promise to resolve, and then assign the resolved value to a variable.

So we can see that we have two promises, the first one is to get the user data, and the second one is to get the user posts, and we want to get the user data first, then get the user posts, and then log the user data and the user posts.

That where await comes in, we use await to wait for the promise to resolve, and then assign the resolved value to a variable.

We can't use await outside of an async function, so we have to define an async function, and then use await inside it.

Try and Catch

What if the promise fails? let's see how to handle the rejected promise.

const getUserData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject("Error");
    }, 1000);
  });
};

const getUserPosts = (userId) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(["Post 1", "Post 2", "Post 3"]);
    }, 1000);
  });
};

const getUserDataAndPosts = async () => {
  try {
    const user = await getUserData();
    const posts = await getUserPosts(user.id);
    console.log(user, posts);
  } catch (error) {
    console.log(error);
  }
};

getUserDataAndPosts();
Enter fullscreen mode Exit fullscreen mode

In the above example, we have rejected the promise in the first function which retrieves the user data, in that sense, the code will stop executing and will go to the catch block, and the error will be logged, thus the getUserPosts function will not be executed.

But if the opposite happened, the getUserData function is resolved successfully, and the getUserPosts function is rejected, it will execute the first function successfully but it will jump to the catch block and log the error because the getUserPosts function is rejected.

Promise.all

Now let's have another scenario, we have multiple promises but we need to wait for all of them to resolve, and then do something with the resolved values.

For example, let's say we have a function to get the user data, and another function to get the user posts, and we want to get the user data and the user posts at the same time, and then log the user data and the user posts.

Here we can use Promise.all to wait for all promises to resolve, and then do something with the resolved values.

const getUserData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        id: 155,
        name: "Hasan",
        age: 20,
      });
    }, 1000);
  });
};

const getUserPosts = (userId) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(["Post 1", "Post 2", "Post 3"]);
    }, 1000);
  });
};

const getUserDataAndPosts = async () => {
  const [user, posts] = await Promise.all([getUserData(), getUserPosts()]);
  console.log(user, posts);
};

getUserDataAndPosts();
Enter fullscreen mode Exit fullscreen mode

What we did here is we called Promise.all method which receives an array of Promises, it will wait until all promises are resolved successfully then it will return an array of the resolved values.

But what if one of the promises is rejected? let's see how to handle the rejected promise.

const getUserData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject("Error");
    }, 1000);
  });
};

const getUserPosts = (userId) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(["Post 1", "Post 2", "Post 3"]);
    }, 1000);
  });
};


const getUserDataAndPosts = async () => {
  try {
    const [user, posts] = await Promise.all([getUserData(), getUserPosts()]);
    console.log(user, posts);
  } catch (error) {
    console.log(error);
  }
};

getUserDataAndPosts();
Enter fullscreen mode Exit fullscreen mode

The question here, would the Promise.all continue trying to resolve the other promises if one of them is rejected? the answer is no, it will stop trying to resolve the other promises and will jump to the catch block.

Finally Block

The finally block is called in both scenarios, whether the promise is resolved Successfully done or rejected Failed to resolve.

Let's see how to use it first with the then and catch methods.

const getUserData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        id: 155,
        name: "Hasan",
        age: 20,
      });
    }, 1000);
  });
};

const getUserPosts = (userId) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(["Post 1", "Post 2", "Post 3"]);
    }, 1000);
  });
};

const getUserDataAndPosts = () => {
  getUserData()
    .then((user) => {
      return getUserPosts(user.id);
    })
    .then((posts) => {
      console.log(posts);
    })
    .catch((error) => {
      console.log(error);
    })
    .finally(() => {
      console.log("Done");
    });
};

getUserDataAndPosts();
Enter fullscreen mode Exit fullscreen mode

In the above example there will be two logs, the first one is the user posts which is called from then method, and the second one is Done which is called from finally method.

Now let's see how to use it with async and await.

const getUserData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        id: 155,
        name: "Hasan",
        age: 20,
      });
    }, 1000);
  });
};

const getUserPosts = (userId) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject('Error');
    }, 1000);
  });
};

const getUserDataAndPosts = async () => {
  try {
    const [user, posts] = await Promise.all([getUserData(), getUserPosts()]);
    console.log('Successfully Completed');
    console.log(user, posts);
  } catch (error) {
    console.log('ERROR');
    console.log(error);
  } finally {
    console.log("Done");
  }
};

getUserDataAndPosts();
Enter fullscreen mode Exit fullscreen mode

Here will be also two logs, the first one will be from the catch block because the first promise is rejected, and the second one will be from the finally block.

Promise.allSettled

The Promise.allSettled method is similar to the Promise.all method, but it will wait for all promises to be settled, whether they are resolved or rejected, and then it will return an array of objects, each object will have the status of the promise, and the value or the error.

Let's see how to use it.

const getUserData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        id: 155,
        name: "Hasan",
        age: 20,
      });
    }, 1000);
  });
};

const getUserPosts = (userId) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject("Error");
    }, 1000);
  });
};

const getUserDataAndPosts = async () => {
  const results = await Promise.allSettled([
    getUserData(),
    getUserPosts(),
  ]);
  console.log(results);
};

getUserDataAndPosts();
Enter fullscreen mode Exit fullscreen mode

The result will be an array of objects, each object will have the status of the promise, and the value or the error.

[
  {    
    "status": "fulfilled",
    "value": {
      "id": 155,
      "name": "Hasan",
      "age": 20
    }
  },
  {
    "status": "rejected",
    "reason": "Error"
  }
]
Enter fullscreen mode Exit fullscreen mode

Promise.any

The Promise.any method is similar to the Promise.all method, but it will wait for first promise to be resolved, and then it will return the resolved value.

Let's see how to use it.

const getUserData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        id: 155,
        name: "Hasan",
        age: 20,
      });
    }, 1000);
  });
};

const getUserPosts = (userId) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject("Error");
    }, 1000);
  });
};

const getUserDataAndPosts = async () => {
  const user = await Promise.any([getUserData(), getUserPosts()]);
  console.log(user);
};

getUserDataAndPosts();
Enter fullscreen mode Exit fullscreen mode

The result will be the resolved value.

{
  "id": 155,
  "name": "Hasan",
  "age": 20
}
Enter fullscreen mode Exit fullscreen mode

But what if the first promise is rejected and the second one is resolved, will it return the resolved value or the rejected error? the answer is the resolved value.

const getUserData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject("Error");
    }, 1000);
  });
};

const getUserPosts = (userId) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(["Post 1", "Post 2", "Post 3"]);
    }, 1000);
  });
};

const getUserDataAndPosts = async () => {
  const posts = await Promise.any([getUserData(), getUserPosts()]);
  console.log(posts);
};

getUserDataAndPosts();
Enter fullscreen mode Exit fullscreen mode

Outputs:

["Post 1", "Post 2", "Post 3"]
Enter fullscreen mode Exit fullscreen mode

So Promise.all waits for all resolved promises but Promise.any waits for the first resolved one.

Promise.race

The Promise.race is from its name, will wait for the first resolved promise and resolve it regardless it is order in the array.

Let's see how to use it.

const getUserData = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        id: 155,
        name: "Hasan",
        age: 20,
      });
    }, 2000);
  });
};

const getUserPosts = (userId) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(["Post 1", "Post 2", "Post 3"]);
    }, 1000);
  });
};


const getUserDataAndPosts = async () => {
  const posts = await Promise.any([getUserData(), getUserPosts()]);
  console.log(posts);
};

getUserDataAndPosts();
Enter fullscreen mode Exit fullscreen mode

As we can see, the getUserData will be delay with 2 seconds, but the getUserPosts will be delay with 1 second, but the result will be the getUserPosts because it is the first resolved promise.

Outputs:

["Post 1", "Post 2", "Post 3"]
Enter fullscreen mode Exit fullscreen mode

A note before we conclude

Promises in Javascript has another name in other programming languages called Futures, they are more or less the same concept, just wanted to let you know that because you might hear this name in other programming languages.

🎨 Conclusion

In this article, we learned the concept behind Promises and why and how it is useful in our applications

We also learned about the Promise.all, Promise.allSettled, Promise.any, and Promise.race methods, and how to use them.

☕♨️ Buy me a Coffee ♨️☕

If you enjoy my articles and see it useful to you, you may buy me a coffee, it will help me to keep going and keep creating more content.

😍 Join our community

Join our community on Discord to get help and support (Node Js 2023 Channel).

📚 Bonus Content 📚

You may have a look at these articles, it will definitely boost your knowledge and productivity.

General Topics

Packages & Libraries

React Js Packages

Courses (Articles)

💖 💪 🙅 🚩
hassanzohdy
Hasan Zohdy

Posted on November 19, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

16-ES6++: Reflection In JavaScript
javascript 16-ES6++: Reflection In JavaScript

December 6, 2022

15-ES6++: Proxy In JavaScript
javascript 15-ES6++: Proxy In JavaScript

December 4, 2022

14-ES6++: Null Coalescing in Javascript
javascript 14-ES6++: Null Coalescing in Javascript

November 26, 2022

13-ES6++: Optional Chaining in Javascript
javascript 13-ES6++: Optional Chaining in Javascript

November 26, 2022