This is an authorized translation of @Mohsan Riaz’s medium post. See portal

Writing asynchronous code in Javascript is often disconcerting, especially when using the continuation-passing style(CPS) style. Asynchronous code affects the readability of code, making it harder to decouple into smaller, separate blocks of code, making it more error-prone and harder to change later.

Continuation -passing style: Continuation -passing style: Continuation -passing style: Continuation -passing style: Continuation -passing style: Continuation -passing style: Continuation -passing style: Continuation -passing style: Continuation -passing style: Continuation -passing style: Continuation -passing style: Continuation -passing style See this blog for details

If there was a way to write asynchronous code in a similar style to synchronous code, our programming lives would be much more enjoyable. Promises using JavaScript can do this.

Let’s start by trying to figure out what’s wrong with using callback.

Callbacks

For example, if you need to get a list of countries, then get the list of cities in the first country in the list of countries, then get all the universities in the list of cities, and finally show the first university in the first city.

Because each call depends on the results of the previous call, a series of nested callback functions are called.

function fetchCountries(){
  fetchJSON("/countries".(success, countries) = > {
    if(success){
      try{
        // do some stuff
        fetchCities(countries[0].id);
      } catch(e){ processError(); }}else
      processError();
  });
}

function fetchCities(countryId){
  fetchJSON(`countries/${countryId}/cities`.(success, cities) = > {
    if(success){
      try{
        // do some stuff
        fetchUniversities(cities[0].id);
      } catch(e){
          processError()
      }
    }else
      processError();
  });
}

function fetchUniversities(cityId){
  fetchJSON(`cities/${cityId}/universities`.(success, universities) = > {
    if(success){
      try{
        // do some stuff
        fetchUniversityDetails(universities[0].id);
      }catch(e){ processError(); }}else
      processError();
  });
}

function fetchUniversityDetails(univId){
  fetchJSON(`/universities/${univId}`.(success, university) = > {
    if(success){
      try{
        // do some stuff
        hideLoader();
      }catch(e){ processError(); }}else
      processError();
    
  });
}
Copy the code

What’s wrong with the above implementation?

Instead of putting asynchronous logic in the same place, the callback function decides which function to call next. In short, we give the functions a flow of control that tightly couples them together.

Why Promise

In my opinion, promises have four major advantages over a simple continuation-passing style:

  1. Better definition of asynchronous logic control flow
  2. The decoupling
  3. Better error handling
  4. Improved readability

The use of the Promise

Javascript promises tell asynchronous code to return a value like synchronous code, which is an object that promises success or failure. This small change makes it very powerful to use.

get("/countries")
  .then(populateContDropdown)
  .then(countries= >  get(`countries/${countries[0].id}/cities`))
  .then(populateCitiesDropdown)
  .then(cities= > get(`cities/${cities[0].id}/universities`))
  .then(populateUnivDropdown)
  .then(universities= > get(`universities/${universities[0].id}`))
  .then(populateUnivDetails)
  .catch(showError)
  .then(hideLoader)
Copy the code

Similar to synchronous programming, the output of one function is the input of the next. JS functions are used in the call chain as with json.parse, and their return values are fed to the next callback function.

If there is an exception, the catch function will catch it. The following code will run as usual, and the loader in the above example will be hidden after execution.

contrast

We define all asynchronous logic in the same place, without having to do extra checking or use try/catch for error handling. As a result, the code has higher readability, lower coupling, and reusable independent functions.

Error handling details

Whenever an intentional or unintentional exception occurs, it is thrown in the next catch handler. You can place a catch handler anywhere in the chain to catch a particular exception and display an error or revalue.

get("/countries")
  .then(JSON.parse)
  .catch(e= > console.log("Could not parse string"))
  .then(...)
  .catch(e= > console.log("Error occured"))
Copy the code

In the example above, the second console statement will not be printed.

If an exception has been caught and you want to pass it to the next catch handler, it must be thrown.

This is handy if you want to display multiple error messages, for example:

get("/coutries")
  .then(...)
  .catch(e= > {
    showLowLevelError(); 
    throw e;
  })
  .catch(showHighLevelError)
Copy the code

Promise vs Event listener

Event listeners are useful if an event is expected to be fired more than once, such as a button click event. Promises are similar to event listeners, but they differ in two essential ways:

  1. Promise will only be resolved once, whether it’s abnormal or normal.
  2. Even if the callback function added after the Promise decision is still called, this means you can check later that the Promise isfullfiledorrejected.

This is useful because we are more interested in reacting to outcomes that occur. Here’s a simple example of image preloading:

var imagePromise = preloadImage("src.png");
setTimeout(() = > {
  imagePromise.then(() = > console.log("image was loaded") )
     .catch(() = > console.log("could not load the image"))},2000)

function preloadImage (path) {
  return new Promise((resolve, reject) = > {
    var image = new Image();
    image.onload  = resolve;
    image.onerror = reject;
    image.src = path;
  });
};
Copy the code

Promise.all([…] ) is useful for bulk resolution

Promise.all([imagePromise1, imagePromise2, ....] ) .then(...) .catch(...)Copy the code

conclusion

In most JavaScript practices, promises are very useful, especially if the success or failure callback can only be executed once. But in some practices, especially those where event callbacks are called multiple times, plain callbacks are better. Thank you for reading this article, and if you have any questions, please leave them in the comments section.

First translation, if you have any questions welcome to correct.