Native JavaScript promises – part3 : Error Handling & Chaining

Error Handling:

Error handling can be done via the catch() method or as the second parameter of the then() method. Consider these equivalent chunks of code:

//Option 1  
getJSON('../data/file.json') //error happening here
.then(function(data){
  updateView(data);
  return get(data.thisUrl); //or error happening here
})
.catch(function(error){ //

 

In some cases, then() can be interpreted as a catch():

getJSON('../data/file.json')
//There is no resolve function here, only a rejection function
.then(undefined, rejectFunction)
//In the eventuality that the promise resolves, 
//the following then() that has a resolve function will be used
.then(resolveFunction);

 

In all cases, as soon as a promise rejects, the JavaScript engine skips to the next reject function in the chain:

//Option 1  
getJSON('../data/file.json') //error happening here
.then(function(data){
  updateView(data);
  return get(data.thisUrl); //or error happening here
})
.catch(function(error){ //

 

Important note on error handling:

It is strongly recommended to use catch() instead of then() when trying to handle errors for two reasons:

  1. Code readability: catch() are easy to spot in the code, using then() for both resolving and rejecting can be confusing
  2. Execution order: The resolve and rejection functions will never both be called if they are part of the same then() while they both can be called if the rejection function is part of a catch()

 

Performing asynchronous actions:  series vs parallel

Most asynchronous operations are not isolated, one request can lead to many others. In the case of multiple asynchronous actions, they all get performed in a chain (one promise generating another promise). The performance will then differ depending on wether actions are performed in series or in parallel.

/**
 * Chaining promises:
 * Two file requests must be performed because one resource leads to the other.
 * We assume: 
 * createThumbnail(data) receives an object, generates a thumbnail and place it on stage
 * get(url) : returns a promises (produced from fetch() of the fetch API)
 * getJSON(url) : performs an XHR for a JSON and returns a promise that passes the parsed JSON response
*/
//On page load: 
  getJSON('../data/file1.json')//Make the 1st request
  //If 1st request successful, return a promise of the 2nd request
  .then(function(resource){  
    return getJSON( resource.results[0]);
  })
  //Handling errors on the 1st request
  .catch(function(){
    throw Error('Search Request Error');
  })
  //If 2nd request is successful, create thumbnail
  .then(createThumbnail)
  //Handling errors on the 2nd request
  .catch(function(error){
    console.log('error = ', error); 
  });
//On page load: 

 

Actions in series:

Actions in series occur one after another, which means that the next resource is also fetched after the current one resolves.

/**
 * Situation where two file requests must be performed in order to 
 * get the information necessary to display a thumbnail on stage.
*/
getJSON('../data/file.json')//1st request
.then(function(response) {  
  //generate a promise object that will be reused to generate a sequential request
  let sequence  = Promise.resolve();
  //For each url in the array ...
  //chain a new promise that will be resolved only after the preceeding one 
  response.results.forEach(function(url){
    sequence.then(function(){
      return getJSON(url);
    })
    .then(createPlanetThumb)
    .catch(function(error){
      throw Error('Search Request Error = ', error);
    });
  }); 
});
//https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve

 

Actions in parallel:

Actions in parallel all occur simultaneously, which means that resources are fetched and probably resolve at the same time.

Actions in parallel using Array.prototype.map():

Promises can be resolved in parallel using the map() method of the Array object like in the following example (resolve order is not guaranteed):

/**
 * We use the map() on an array of url resources and generate 
 * a promise for each url resource of the array
 * (We assume errors are handled)
*/
getJSON('../data/file.json')//1st request
.then(function(resourse){  
  resourse.results.map(function(url){ 
    getJSON(url).then(createPlanetThumb);
  });
});

 

Actions in parallel using Promise.all():

Promises can also be resolved in parallel using Promise.all() method which will simultaneously resolve all promises provided in an iterative argument (or reject them all only one of them rejects).

/**
 * We use the map() on an array of url resources and generate 
 * a promise for each url resource of the array
 * createThumbnail(data) : received an object, generates a thumbnail and place it on stage
 * getJSON(url) : performs an XHR for a JSON and returns a promise that passes the parsed JSON 
*/
getJSON('../data/file.json')//request the array of urls
.then(function(response) { 
  //returns a promise that resolves all promises promises generated in the array
  return Promise.all(response.results.map(getJSON)); 
})
.then(function(arrayOfResources){
  //create a thumbnail for each object in the array
  arrayOfResources.forEach(function(res){
    createPlanetThumb(res);
  });
})
.catch(function(error){
  console.log(error);
});

 

Conclusion:

Today, we looked into error handling, promises chaining and we finished by studying two ways of  performing multiple asynchronous operations (series and parallel).

This concludes our three parts studies on Native JavaScript Promises. Thank you.

 

Native JavaScript promises – part2 : Effective use

Syntax

A promise can be seen as a constructor that wraps a “try-catch” around the asynchronous code. Here is the syntax of a basic promise (more syntax variation on MDN):

/**
 * Basic promise syntax with 3 states: pending, success and failure
*/
new Promise(function(resolve){
   console.log('Still in process (pending)');
   resolve();
}).then(function(){
   console.log('Resolved (success)');
}).catch(function(){ 
   console.log('An error happened (failure)');
   //Dealing with the error here!
}); 

 

Use case: Acting on the page before it fully loads

A promise can be used to “spy” on the document and create an action at a specific moment. Suppose we want something done on the page before sub resources are loaded (images, stylesheets, …), we can create a promise that resolves only when the page reaches a precise loading state and then call and action when that promise resolves.

/** 
 * Create a promise that resolves only when the document 'ready state' 
 * is not on "loading" and call an action when that promise resolves
 * We assume: 
 * doSomethingOnTheDOM() updates the DOM with whatever feedback we choose
*/ 
//Wrap an event listener for readystatechange in a Promise.
function ready() { 
  return new Promise(function(resolve){
    function checkState (){
      //Only resolves if document is not 'loading'
      if(document.readyState !== 'loading'){
        resolve(); 
      }
    }
    //Check ready state when 'readystatechange' event fires
    document.addEventListener('readystatechange', checkState);
    checkState();//also checks ready state immidiately
  });
};//ready()

//Update the DOM only when the promise resolves. 
ready().then(doSomethingOnTheDOM);

 

Fetch API, a real game changer:

The Fetch API makes the process of fetching resources a breeze by providing an elegant interface that encapsulates native JavaScript Promises and handle results and callbacks (checkout also David Walsh article).

In the following example, we won’t worry about writing the XHR request, nor  parsing the resulting resource ourselves.

//Outpout something on the DOM
//Uses fetch (fetch API) to make and XHR request and return a promise
function get(url) {
  return fetch(url); //fetch assumes the method is 'get'
}

//Performs an XHR request and returns a parsed JSON response.
function getJSON(url) { 
  return get(url).then(function(response){
    //Returns a promise that resolves with a JSON object.
    return response.json();
  });
}

//When document is ready ...
  getJSON('../data/file.json')
  .then(function(response){
    doSomethinOnTheDOM(response);
    return response; //can be reused for chaining
  });
//When document is ready ...

 

Conclusion:

We’ve just seen a basic syntax of Native JavaScript Promises and looked at one possible use case where a promise is used to target a precise loading stage of the page. We’ve finally looked at a simple use of the Fetch API.

Next time, we’ll look at native JavaScript promises’ chaining and error handling.

 

Native JavaScript promises – part1 : The concept

Many applications today are relying on network requests, threads, events or some kind of process that has an uncertain outcome. These types of operations which are called asynchronous are unpredictable and error prone. Promises are the recommended option for dealing with asynchronous operations because they are flexible, intuitive and make error handling easy.

Our goal in this first post, will be to explore the concept of native JavaScript promises and understand their use in modern web development.

 

A Promise represents a value which may be available now, or in the future, or never. The Promise object is used for asynchronous computations. – MDN –

 

Synchronous and Asynchronous operations:

Synchronous Operations: sequencial executions

The JavaScript threading model is synchronous, which means that the code runs in a single and unbroken timeline (one statement executes immediately after another one).

/**
 * Example of a synchronous code: One statement is executed
 * before the next one gets executed (guaranteed)  
*/
let soccer_player = "Samuel Eto'o"; //first executed
console.log("Player name is : ", soccer_player); //second executed (Samuel Eto'o)

 

Asynchronous Operation: uncertainty of outcome

Unlike synchronous code, asynchronous code is not guaranteed to execute in a single timeline. It is best to assume that the completion time of such operation is unknown.

/**
 * Example of an asynchronous code: Two files are requested 
 * and there is no way to know if "file1" will return before 
 * "file2" or if even any of them will return at all. 
*/
let file1 = get('file1.json'); //file1 is first requested
let file2 = get('file2.json'); //file2 is requested next
//File aren't yet available
console.log(file1); //undefined
console.log(file2); //undefined

 

Not knowing when a request ends is a serious issue for the proper running of an application and this is where Promises come to the rescue.

 

The promise: easy handling of asynchronous operations

 Definition: 

A promise is a try-catch wrapper around the code that will finish at an unpredictable time. It allows the programmer to instruct the application about how to deal with something that is expected to happen in the future without knowing exactly when.

Different states of promise:

A promise has four states which represent the possible outcomes of an asynchronous operation. The developer can then use these states to deal with the outcomes:

  1. Fulfilled (resolved): The operation was successful
  2. Rejected: The operation failed
  3. Pending: The operation is still in process
  4. Settled: The operation is done (either succeeded or failed)

What can we use promise for?

  • To turn incoming external data (JSON) into useful information in an app
  • To work with information from ajax requests (ajax is an asynchronous request)
  • To work with threading applications like messaging systems
  • To control an application’s flow (doing something when all resources of the page aren’t completely loaded)

 

Conclusion:

We’ve just seen that operations in JavaScript are not always synchronous (instructions executed in order in appearance), but can also be asynchronous (instructions executed in the future with an unpredictable timeline and outcome).  We have seen that promises deal well with asynchronous operations by wrapping them with a “try and catch” that allows the developer to prepare for the operation’s possible outcomes: “success”, “failure”, “in process” and “completion”.

Next time, we’ll look at the syntax and use cases of native JavaScript promises.