DEV Community

Cover image for Why JavaScript Promises are awesome
Methmi
Methmi

Posted on

Why JavaScript Promises are awesome

What is a Promise?

A promise, in computer science, is basically a concept that handles a value that is to be produced in the future, after an asynchronous operation completes successfully or if it does not, gracefully handles a failure.

Too much jargon?

Here’s a high level picture -

You may lend something to a friend and trust them enough to know that you won’t have to stop whatever you were doing and wait for them to return it. You know that they will return it eventually, so, you’ll be able to go one with your life until the item (or an excuse 😅) is returned.
Although you will act on the eventual outcome of the promise that the friend gave you, you will save that for the time the promise is actually fulfilled.

This was the concept in mind when the term Promise (in computer science) was first coined in 1976.

Promise concepts were popularized in the JavaScript world by the jQuery Deferred Objects, that were similar in concept to the Promise, but differed from the current specification of Promises in ECMAScript 2015.

After their official acceptance into the ECMAScript 2015 spec, they are now implemented in all the latest browsers and Node, and are here to cleanup the potential muddle in callbacks and simplify the process.

What about Callback functions?

Callbacks also produce values after an asynchronous operation but executing one asynchronous operation after the other requires callbacks to be called inside another callback, as shown below.

The callbacks being nested deeper and deeper cause the code to expand horizontally as well, leading to an error-prone and confusing code block understandably known as ‘Callback Hell’.

Promises are used to bypass this.

Using Promises instead

A promise can be created with its constructor:

let promise = new Promise(function(resolve, reject) {
   // Your application logic goes here
});
Enter fullscreen mode Exit fullscreen mode

The function taken into the constructor is the executor function and takes in two arguments: Resolved and Rejected.

The code inside this function is the logic to be executed each time a new Promise is created.

The Promise returned from the constructor should be able to inform the executor function, handling async (asynchronous) operations, when execution has started, completed or returned an error.

Parts of a promise

A promise has a STATE and a RESULT

A promise has 3 states:

  • Pending

  • Fulfilled

  • Rejected

Pending promises in JavaScript, much like in the real world, is a promise that has been executed but not yet completed and can therefore move to the ‘Fulfilled’ or ‘Rejected’ state.

Fulfilled promises are resolved or completed, indicating a successful outcome.

Rejected promises indicate an unsuccessful outcome due to an error or a timeout.

And, promises that are either Fulfilled or Rejected are called Settled.

The result property of a promise can hold the values:

  • undefined : When the state is pending

  • value : When resolve(value) is called

  • error : When reject(error) is called

Resolving and Rejecting

The promise can be resolved with resolve() and placed in the fulfilled state, or rejected with an error as reject(new Error('Something went wrong')) in and placed in rejected state.

let myPromise = new Promise(function(resolve, reject) {
   resolve("This will be resolved"); // EXECUTED
   reject(new Error('Something went wrong')); // ignored
   resolve("Resolved again?"); // ignored
});
Enter fullscreen mode Exit fullscreen mode

In this example, reject() and resolve() are being called again after the promise has been fulfilled.

But, once the state has changed, any further calls to reject and resolve will be ignored.

Handling a promise

The handler functions .then(), .catch(), .finally() allow functions consuming the promise to be in sync with the executor function when a promise is fulfilled/rejected.

Using .then()

This is called to handle a promise’s rejection or fulfillment, and can therefore accept up to two functions as arguments:

myPromise.then(
  (result) => { // a successful outcome, promise fulfilled
      console.log(result);
   },
   (error) => { // promise rejected
      console.log(error);
   }
);
Enter fullscreen mode Exit fullscreen mode

Or if only one of the two outcomes are needed:

// logs SUCCESFUL outcomes only
myPromise.then(
   (result) => {
      console.log(result);
   }
);
// logs ERROR outcomes only
myPromise.then(
   null,
   (error) => {
      console.log(error);
   }
);
Enter fullscreen mode Exit fullscreen mode

Using .catch()

Leaving one argument null in .then() to handle errors is not so useful. This is when .catch() used.

In the following example getPromise() is a user implemented function that accepts a URL and returns a promise stating its outcome.

let urlPromise = getPromise(BAD_URL);
   const consumerFunc = () => {
   promise.catch(error => console.log(error));
}
consumerFunc ();
Enter fullscreen mode Exit fullscreen mode

Using .finally()

This handler cleans up after an execution (for example, close a live connection) and will run regardless of whether a promise is rejected or fulfilled.

Here is .finally() being used with the other handlers:

let isLoading = true;
isLoading && console.log('Loading...');
//Promise with outcome of URL
promise = getPromise(A_URL);
promise.finally(() => {
   isLoading = false;
   console.log(`Promise settled and loading is ${isLoading}`);
}).then((result) => {
   console.log({result});
}).catch((error) => {
   console.log(error)
});
Enter fullscreen mode Exit fullscreen mode
  • .finally() will set isLoading to false.

  • If the URL is valid, .then() is executed and result is logged.

  • If the URL is invalid, error is caught and .catch() is executed and error is logged.

  • .finally() will be called regardless of a resolved or rejected promise.

Chaining Promises

aPromise.then() always returns a promise this lets us call .then() on the new promise leading to a chain of .then() methods passing down a single promise.

A .then() can return a new promise, or throw an error too. The following example demonstrates this:

let myPromise = getPromise(URL_RETURNING_MULTIPLE_ITEMS);
promise.then(result => {
   // extracts URL of oneItem
   let oneItem = JSON.parse(result).results[0].url;
   return oneItem; // returns the URL
}).then(oneItemURL => {
   console.log(oneItemURL);
   return getPromise(oneItemURL); // new promise returned
}).then(itemData => {
   console.log(JSON.parse(itemData));
}).catch(error => {
   console.log(Error caught. In the Catch block, error);
});
Enter fullscreen mode Exit fullscreen mode

Where the new promise is created, the URL from the previous .then() is passed into getPromise() which returns the new promise that is then resolved sent down the chain where itemData is logged.

If an error is caught, the .catch() in the chain is triggered and the error is handled.

And those were the basics of how Promises can be used for asynchronous functions, resulting in clean, understandable code that’s less prone to errors.

Till next time, happy coding!

Latest comments (12)

Collapse
 
shshank profile image
Shshank

Nice article. Thanks for sharing. You helped me clear more points on promises which helps me a lot in my projects.

Collapse
 
methmi profile image
Methmi

Thanks, glad it helped you!

Collapse
 
mightytechno profile image
Mighty

Great article

Collapse
 
methmi profile image
Methmi

Thanks!

Collapse
 
silvestrik profile image
silvestrik

It was really useful. Thank’s!

Collapse
 
sebasprogrammer2021 profile image
Sebastián Londoño Valencia

same here,

Collapse
 
chiroro_jr profile image
Dennis

Great article

It really helped me think deeply about promises. A promise is basically an object that represents the result of an asynchronous operation. That result can be your desired value or an error. For example if you are making a fetch request for some posts then the desired result is the array of posts. If something goes wrong on the server then the result is an error telling you what went wrong. Either way you get a result from the promise, a desired value or an error.

Collapse
 
methmi profile image
Methmi

Thank you 🙌

Collapse
 
iaboelsuod profile image
Ibrahim Zahema

Great job on your first article 🎉. You totally should give observables (and rxjs) a look though.

Collapse
 
methmi profile image
Methmi

Will do 🙌 Thanks for the suggestion!

Collapse
 
sokhavuth profile image
Sokhavuth TIN • Edited

To highlight JavaScript syntax, you just write the word JavaScript or js after the opening triple quote:

let myPromise = getPromise(URL_RETURNING_MULTIPLE_ITEMS);
promise.then(result => {
   // extracts URL of oneItem
   let oneItem = JSON.parse(result).results[0].url;
   return oneItem; // returns the URL
}).then(oneItemURL => {
   console.log(oneItemURL);
   return getPromise(oneItemURL); // new promise returned
}).then(itemData => {
   console.log(JSON.parse(itemData));
}).catch(error => {
   console.log(Error caught. In the Catch block, error);
});
Enter fullscreen mode Exit fullscreen mode
Collapse
 
methmi profile image
Methmi

Thank you so much for that input!