loading...
Cover image for An introduction to promises

An introduction to promises

myogeshchavan97 profile image Yogesh Chavan ・5 min read

Introduction

Promises are one of the most important yet confusing and difficult to understand part of JavaScript, that most freshers, as well as experienced developers, struggle to understand.

So in this multi-part series, we'll explore all about promises, async/await, various promise methods and how to use promises for building real-world applications.

So let's get started with this first part in the series.

Promises

Promises are added in ES6 as a native implementation.

Promise represents an asynchronous operation to be completed in the future.

Previously, Before ES6, there was no way to wait for something to perform some operation.

For example, when we make an API call, there was no way to wait until we get the result back before ES6 came.

For that, We have used external libraries like Jquery or Ajax which had their own implementation of promises but there was no browser implemented promise thing.

But Now using Promises in ES6, we can make an API call ourselves and wait until it's done to perform some operation.

How to create a Promise

To create a promise we need to use the Promise constructor function like this:

const promise = new Promise(function(resolve, reject) {

});
Enter fullscreen mode Exit fullscreen mode

The Promise constructor takes a function as an argument and that function internally receives resolve and reject as parameters.

The resolve and reject parameters are actually functions that we can call depending on the outcome of the asynchronous operation.

Promise goes through three states.

  • Pending
  • Fulfilled
  • Rejected

When we create a promise, it’s in a pending state, and when we call the resolve function, it goes in a fulfilled state and if we call reject it will go in the rejected state.

To simulate the long-running or asynchronous operation, we will use the setTimeout function.

const promise = new Promise(function(resolve, reject) {
 setTimeout(function() {
  const sum = 4 + 5;
  resolve(sum);
 }, 2000);
});
Enter fullscreen mode Exit fullscreen mode

Here, we've created a Promise which will resolve to the sum of 4 and 5 after 2000ms(2 seconds) timeout is over.

To get the result of the successful promise execution, we need to register a callback using .then like this:

const promise = new Promise(function(resolve, reject) {
 setTimeout(function() {
  const sum = 4 + 5;
  resolve(sum);
 }, 2000);
});

promise.then(function(result) {
 console.log(result); // 9
});
Enter fullscreen mode Exit fullscreen mode

If the operation is not successful, then we call reject function like this:

const promise = new Promise(function(resolve, reject) {
 setTimeout(function() {
  const sum = 4 + 5 + 'a';
  if(isNaN(sum)) {
    reject('Error while calculating sum.');
  } else {
    resolve(sum);
  }
 }, 2000);
});

promise.then(function(result) {
 console.log(result);
});
Enter fullscreen mode Exit fullscreen mode

Here, If the sum is not a number then we're calling the reject method with the error message otherwise we're calling the resolve method.

If you execute the above code, you will see the following output:

Error without catch

As you can see, we're getting an uncaught error message along with the message we've specified because calling reject function, throws an error but we have not added any error handler for catching that error.

To catch the error, we need to register another callback using .catch like this:

promise.then(function(result) {
 console.log(result);
}).catch(function(error) {
 console.log(error);
});
Enter fullscreen mode Exit fullscreen mode

You will see the following output:
Error with catch

As you can see, we have added the .catch handler, so we're not getting any uncaught error but we're just logging the error to the console.

This also avoids stopping your application abruptly.

So it's always recommended to add .catch handler to every promise so your application will not stop from running because of the error.

Promise chaining

We can add multiple .then handlers to a single promise like this:

promise.then(function(result) {
 console.log('first .then handler');
 return result;
}).then(function(result) {
 console.log('second .then handler');
 console.log(result);
}).catch(function(error) {
 console.log(error);
});
Enter fullscreen mode Exit fullscreen mode

When we have multiple .then handlers added, the return value of the previous .then handler is automatically passed to the next .then handler.

Promise Chaining

As you can see, adding 4 + 5 resolves a promise and we get that sum in the first .then handler. There we're printing a log statement and returning that sum to the next .then handler.

And inside the next .then handler, we're adding a log statement and then we're printing the result we got from the previous .then handler.

This way of adding multiple .then handlers is known as promise chaining.

Delaying a promise execution

Many times we don't want to create promise immediately. We want to create a promise after some operation is completed.

To achieve this, we can wrap the promise in a function and return that promise from that function like this:

function createPromise() {
 return new Promise(function(resolve, reject) {
  setTimeout(function() {
   const sum = 4 + 5;
   if(isNaN(sum)) {
     reject('Error while calculating sum.');
   } else {
    resolve(sum);
   }
  }, 2000);
 });
}
Enter fullscreen mode Exit fullscreen mode

This way, we can use the function parameters inside the promise, making the function truly dynamic.

function createPromise(a, b) {
 return new Promise(function(resolve, reject) {
  setTimeout(function() {
   const sum = a + b;
   if(isNaN(sum)) {
     reject('Error while calculating sum.');
   } else {
    resolve(sum);
   }
  }, 2000);
 });
}

createPromise(1,8)
 .then(function(output) {
  console.log(output); // 9
});

// OR

createPromise(10,24)
 .then(function(output) {
  console.log(output); // 34
});
Enter fullscreen mode Exit fullscreen mode

Output

Note: When we create a promise, it will be either resolved or rejected but not both at the same time. So we cannot add two resolve or reject function calls in the same promise.

Also, we can pass only a single value to resolve or reject function.

If you want to pass multiple values to resolve function, pass it as an object like this:

const promise = new Promise(function(resolve, reject) {
 setTimeout(function() {
  const sum = 4 + 5;
  resolve({
   a: 4,
   b: 5,
   sum
  });
 }, 2000);
});

promise.then(function(result) {
 console.log(result);
}).catch(function(error) {
 console.log(error);
});
Enter fullscreen mode Exit fullscreen mode

Resolving object

Using arrow functions

In all the above code examples, we've used normal ES5 function syntax while creating promises. But it's a common practice to use arrow function syntax instead of ES5 function syntax like this:

const promise = new Promise((resolve, reject) => {
 setTimeout(() => {
  const sum = 4 + 5 + 'a';
  if(isNaN(sum)) {
    reject('Error while calculating sum.');
  } else {
    resolve(sum);
  }
 }, 2000);
});

promise.then((result) => {
 console.log(result);
});
Enter fullscreen mode Exit fullscreen mode

You can either use ES5 or ES6 function syntax depending on your preference and need.

Avoid Callback Hell

Promises also help to avoid the callback hell problem.

This is the frequently asked Interview question about promises.

To understand it in details with various live code samples, check out my previous article here

Conclusion

Promises are the most powerful and useful feature added in ES6 which helps to avoid callback hell problem and perform asynchronous operation easily.

Check out the next part in this series about async/await here

Don't forget to subscribe to get my weekly newsletter with amazing tips, tricks and articles directly in your inbox here.

Discussion

pic
Editor guide