Being asynchronous in JavaScript using Promises

This post is long overdue. Promises are something I've enjoyed working with the last couple of months.

Not only is it a simple way of working asynchronously, but with the increasing amount of frameworks embracing it, it becomes easy to benefit from in your projects.

So what is a Promise?

A promise is an object that represents an asynchronous computation that eventually will complete. This way we don't have to wait for the computation to finish. Instead we can create multiple promises that can do different work, and we can handle the result when they're finished.

A successful promise

Let's start by looking at a simple promise that finishes successfully.

Say we got a function that fetches some articles - getArticles.

This is a quite heavy operation and we don't want to stick around waiting for the result. So instead we want to execute it in a promise - making the function run asynchronously.

new Promise((resolve) => resolve(getArticles()));  

As you can see, creating a promise is quite easy. You simply initialise a promise object where we send in a function that wraps the things we want done - in our case getArticles(). To complete the promise, we call resolve with the result of the computation.

Adding callbacks

But how do we handle the result of a promise?

Since we're not sticking around waiting for the promise to complete, but letting it continue asynchronously on it's own, we need some way of handling the result.

E.g. In the previous example we fetched some articles, but we didn't do anything with them.

Now, say we want to send the articles to another function - populateUi - to be displayed.

To append the populateUi to the promise, we use then - a function on the promise object for adding callbacks.

new Promise((resolve) => resolve(getArticles()))  
  .then(articles => populateUi(articles));

Handling errors

As you know, computations could also go wrong. Errors could be thrown, things could timeout, and other unexpected behavior happens all the time. This needs to be handled.

As we've seen, resolve is used for successful execution of promises, so how do we notify unexpected behavior?

Simple! Instead of using resolve, we use a function called reject that also is available in the function we send to the promise.

To illustrate this, let's reject the promise with a message if getArticles returns empty.

new Promise((resolve, reject) => {  
  const articles = getArticles();
  articles ? resolve(articles) : reject ("No articles found");

What happens with thrown errors?

So now that we know how we can complete a promise as unsuccessful, what would happen if an Error is thrown?

In this scenario reject would automatically be used to complete the promise.

Adding callbacks handling errors

We need a way to handle possible errors. As with our successful promise, this can be done by adding a callback.

There are two ways of adding a callback that would handle an unsuccessful promise.

One way is to send a second handler to the then function.

new Promise((resolve, reject) => {  
  const articles = getArticles();
  articles ? resolve(articles) : reject ("No articles found");
.then(articles => populateUi(articles),
      err => createError(err));

Another way is to use the catch function.

then and catch can be added after each other. In this case, if the promise is successful, the then callback will be used. However, if the promise was rejected, the catch callback would be used.

new Promise((resolve, reject) => {  
  const articles = getArticles();
  articles ? resolve(articles) : reject ("No articles found");
.then(articles => populateUi(articles))
.catch(err => createError(err));

Possibility to recover.

What I love about promises, is that it allows you to recover in a simple way.

Since catch returns a promise just like new Promise and then, we can try to recover to give a better user experience.

Say getArticles function fails really bad, but we want to display a backup article instead of an error.

To do this we can define a catch that returns an array with the backup article. By placing this straight after our initial promise, we're able to recover and proceed as normal to populateUi.

new Promise((resolve, reject) => {  
  const articles = getArticles();
  articles ? resolve(articles) : reject ("No articles found");
.catch(err => [getBackupArticle()])
.then(articles => populateUi(articles));

Handle multiple promises in one callback

There may be scenarios where we would like to wait for multiple promises before we do any actions with the results.

In this scenario, we can use Promise.all.

What this operator does is to create a new Promise that fulfils if all promises it contains complete successfully.

To see how this is done, say we not only want to fetch articles using getArticles, but also fetch popular topics using popularTopics. However, we don't want to populate the ui before both articles and topics are retrieved.

Now let's make it into code.

  new Promise(resolve => resolve(getArticles())),
  new Promise(resolve => resolve(popularTopics()))])
.then(([articles, topics]) => {

Success! We wait for both getArticles and popularTopics promises before executing the callback which populates the UI.

It's worth mentioning that by using Promise.all, if one rejects, then the whole thing rejects.

Execute callback on the first promise that completes

Now say we have multiple promises, but we want to execute a callback on the first one that finishes.

To do this, we can use Promise.race.

As with Promise.all, Promise.race takes several promises, creating one promise.

However, Promise.race will return when the promise that finishes first completes.

To show how this works, say we got two sources of getting articles. We want to return articles as quick as possible, so we create a promise for each of the sources, then wrapping it with Promise.race.

Now let's look at it as code.

  new Promise((resolve) => resolve(getArticles())),
  new Promise((resolve) => resolve(otherSource()))
.then(articles => populateUi(articles));

Do you want to see more articles like this? Let me know in the comments!