What is a JavaScript Promise?

What is a JavaScript Promise?

After reading this, you won't need another article to understand promises in JS, I promise!

ยท

5 min read

Consider the following scenario:

Your plan was to drink some tea while you read a book. However, when you were about to start, you noticed that there was no sugar in the tea. Instead of completely aborting your plan, you send for some sugar, keep aside the tea and begin reading. When the sugar arrives, you are notified. You pause reading, mix up the tea with the sugar, and go back to your initial plan.

JavaScript Promise illustration

In the programming world, this pattern is termed an Asynchronous Pattern. A language is said to be async when two or more different processes are executed simultaneously, but how does this relate to promises in JavaScript?

While JavaScript is a single-threaded programming language, promises provide a powerful async pattern in JavaScript, so it is something you'll need to invest your time to learn and lucky for you, you're in the right place for that.

In this tutorial, we'll go over what promises are and how to use them. We will look at promises in-depth in the next tutorial. So make sure to read that after you're done with this.

What's a Promise?

I'll define JavaScript promise to you in a few points:

  1. A Promise is simply a JavaScript object: It is an object with properties and methods, and we use some of those methods to interact with the object.

  2. The Promise object represents the eventual completion (or failure) of asynchronous operation: The object represents something that will happen in the future. Suppose you want to fetch data from an API, the API can also respond at a future time. Thus, a promise object will be created to represent the eventual response from the API. While data is still being fetched from the API, the promise can be said to be pending.

  3. Provides a value on completion: When the completion occurs (i.e when this future event occurs), a value is provided. So a promise will always provide a value eventually. Going back to our API example, the value can be a success message from the API or an error message.

Now let's take a look at some promises in JavaScript code.

How to use JavaScript Promises

I stated earlier that we will not focus much on how to create promises in this tutorial, but on how to use them.

Let's assume that asyncFunction is a function that performs some asynchronous task in our code:

function asyncFunction() {
  // Do something that takes time

  // return a promise
   return new Promise((resolve, reject) => {
     resolve('Success!');
}

Note: reject signals a failed promise.

An async task is any task in our code that takes some time to resolve. Fetching data from an external resource is asynchronous, so is using setTimeout(), setInterval() etc. All of these are great candidates for the use of promises.

Now, going back to our function that returns a promise, we need to make use of a promise when it eventually resolves. How do we do this? We simply nest the method then() to the promise, and from there we can read its values:

NOTE: then() allows us to respond to a promise in both success and failure

let promise = asyncFunction() // async function takes time and returns a promise

promise.then(function(val) {
  console.log("Yeah" + val)
})

Here we do something when it's resolved - we pass a callback function into then() that is invoked once the promise is resolved. We could actually pass two functions, not just one.

let promise = asyncFunction() // async function takes time and returns a promise

promise.then(function(val) {
  console.log("Yeah" + val)
},
function(err) {
  console.log("Oops!" + err.message)
}
)

Regarding this, note the following:

  • the first function we pass in will be invoked when it's resolved (aka code runs successfully)
  • the second function we pass in the then() will be invoked if there is an error

Promises are structured so we can chain them together. To understand this, let's suppose you want to make a request to some API. However, when the first promise returns a value, you want to perform another asynchronous task with that value (like making another request).

You can represent subsequent asynchronous tasks in subsequent then() chains:

asyncFunction() // returns a promise
.then(function(val) {
  console.log("Yeah" + val)
  return asyncFunction2() // makes another API call, takes time to resolve
})
.then(function(val) {
  console.log("Yeah" + val)
  return asyncFunction2() // another asynchronous task, returns a promise
})
.then(function(val) {
  console.log("Yeah" + val)
  return val; // finally
})

If you are following well, you might notice that for each of these chains, there is no second function to handle possible errors. So if there is any time of error in the execution of either asyncFunction, asyncFunction1 or asyncFunction2, then there is no way of catching the error.

While you can definitely pass a second function to each of the then() handers, a better way to handle errors in any part of the chain is to nest a catch() at the bottom of the chain:

asyncFunction() // returns a promise
.then(function(val) {
  console.log("Yeah" + val)
  return asyncFunction2() // makes another API call, takes time to resolve
})
.then(function(val) {
  console.log("Yeah" + val)
  return asyncFunction2() // another asynchronous task, returns a promise
})
.then(function(val) {
  console.log("Yeah" + val)
  return val; // finally
})
.catch(function(err) {
  console.log("Oops!" + err)
})

No matter where in the chain the error occurs, this method will be called. We pass a function to handle the error, logging it to the console.

Promise Examples

Note that most of the JavaScript code and libraries today use promises. A notable example of this is the fetch() library. With fetch(), you need to nest a then method to handle the API response.

Let's take a look at a real-life example of this:

fetch('http://localhost:8000/blog?id=1')
      .then(response => response.json())
      .then(res => {
        document.getElementById('title').innerHTML = res.title
        document.getElementById('author').innerHTML = res.author
        document.getElementById('post-body').innerHTML = res.body
      })
      .catch(err => console.error(err));

Here I am making a GET request to an endpoint on my local Node server. Because fetch() returns a promise, I nest two then methods to it. The first does something asynchronous - it parses the response object to JSON.

When the parsing is completed, the second then() is invoked and the callback takes the parsed JSON object and assigns its data to respective DOM elements.

If we encounter an error while fetching from the API or parsing the response to JSON, then catch() will be invoked.

Conclusion

I hope this tutorial helped you understand JavaScript promises better.

async/await provides a better way to work with promises in JavaScript and I'll be writing about it in the next tutorial.

Have a great week.