Voice of a Developer: ECMAScript Promises - Part 32

JavaScript is a language of the Web. This series of articles will talk about my observations learned during my decade of software development experience with JavaScript.

Introduction

A Promise is an asynchronous operation that has not completed now, but will complete in the future. ES6 has adopted promises implementation as native APIs. Promise gives us guarantee to return the value in future. Promises are not like events where you need to bind the event with a particular method.

Creating a promise

The way to create a Promise is by using "new Promise" constructor, which accepts two callback functions as parameters. The first typically named resolve is a function to call with the future value when promise is ready. The second typically named reject is a function to reject the Promise if it cannot resolve the future value.

Syntax

As per MDN, below is the syntax:

new Promise( /* executor */ function(resolve, reject) { ... } );

Executor: A function that will be passed to other functions via the arguments resolve and reject.

Sample code

  1. var p1 = new Promise(function(resolve, reject)  
  2. {  
  3.     if ( /* condition */ )  
  4.     {  
  5.         resolve( /* value */ ); // promise fulfilled   
  6.     }  
  7.     else  
  8.     {  
  9.         reject( /* reason */ ); // mention reason for rejection  
  10.     }  
  11. });  
Let us see what happens at Browser after we trigger above code.

 

  • When code executes “new Promise”, it starts from below state



    The first state of Promise is “pending”.

  • After Promise is fulfilled then it changes to:



    The second state of Promise is “fulfilled” or a promise is “rejected” if it’s not fulfilled.

Summary of Promise states:

  • pending: Initial state, not fulfilled or rejected.
  • fulfilled: Meaning that the operation completed successfully.
  • rejected: Meaning that the operation failed.

Prototype promise methods

After a Promise is created, we need to pass around value of this Promise. Inorder to consume a Promise value we attach a handler using via below methods.

Two methods of Promise are associated with constructor prototype Promise.prototype these are:

then() method

It returns a promise with two arguments, i.e., onFulfilled, on Rejected

  • onFulfilled: a callback function called when promise is fulfilled.
  • onRejected: a callback function is called when promise is rejected.

Example –it will print “Fulfilled!” because we’ve set flag = true,

  1. var flag = true;  
  2. var p1 = new Promise(function(resolve, reject)  
  3. {  
  4.     if (flag)  
  5.     {  
  6.         resolve("Fulfilled!");  
  7.     }  
  8.     else  
  9.     {  
  10.         reject("Rejected!");  
  11.     }  
  12. });  
  13. p1.then(function(value)  
  14. {  
  15.     console.log(value); // Fulfilled!  
  16. }, function(reason)  
  17. {  
  18.     console.log(reason); // Rejected!  
  19. });  
catch() method

It returns a promise and deals with rejected cases only. If you pass null or undefined in .then() method first argument it .then() method behaves like .catch() method.

Example- Below code will print “Rejected!”
  1. var flag = false;  
  2. var p1 = new Promise(function(resolve, reject)  
  3. {  
  4.     if (flag)  
  5.     {  
  6.         resolve("Fulfilled!");  
  7.     }  
  8.     else  
  9.     {  
  10.         reject("Rejected!");  
  11.     }  
  12. });  
  13. p1.then(function(value)  
  14. {  
  15.     console.log(value); // Fulfilled!  
  16. }).catch(function(reason)  
  17. {  
  18.     console.log(reason); // Rejected!  
  19. });  
Example - Chaining promises via .then() method.

As .then(), .catch() methods return Promise so we could chain these [please refer Voice of a Developer JavaScript Chaining part 16 to understand Chaining].

Example- chaining of .then() method and incrementing counter.
  1. var counter = 0;  
  2. var p1 = new Promise(function(resolve, reject)  
  3. {  
  4.     resolve(counter);  
  5. });  
  6. p1.then((value) =>  
  7. {  
  8.     console.log('Counter: ' + ++counter); // 1  
  9. }).then((value) =>  
  10. {  
  11.     console.log('Counter: ' + ++counter); // 2  
  12. }).then((value) =>  
  13. {  
  14.     console.log('Counter: ' + ++counter); // 3   
  15. }).catch(function(reason)  
  16. {  
  17.     console.log(reason);  
  18. });  
Output



Promise methods

Promise.all() –When you are working with multiple promises, this function is really helpful. Once all Promises are resolved or rejected, it returns Promises.
  1. var p1 = new Promise(function(resolve, reject)  
  2. {  
  3.     var counter = 0;  
  4.     resolve(++counter);  
  5. });  
  6. var p2 = new Promise(function(resolve, reject)  
  7. {  
  8.     resolve("counter 2");  
  9. });  
  10. var p3 = new Promise(function(resolve, reject)  
  11. {  
  12.     setTimeout(resolve("Promise 3"), 5000); //5000 milisecond = 5 sec  
  13. });  
  14. Promise.all([p1, p2, p3]).then(function(values)  
  15. {  
  16.     console.log(values); // Output: [1, "counter 2", "Promise 3"]  
  17. }).catch((val) =>  
  18. {  
  19.     console.log(val); // return “rejected”, if any promise fails  
  20. });  
Once all Promisesp1, p2, p3 are fulfilled.then() method is called. The above code .then()will execute after 5 seconds when all Promises are resolved (after setTimeout).

If any promise returns reject, then it will return “rejected”.

Promise.race() – It returns a Promise which resolve or reject first. It accepts an iterable of Promises and works like OR condition.
  1. var p1 = new Promise(function(resolve, reject)  
  2. {  
  3.     setTimeout(resolve, 1500, "resolved - will not get printed");  
  4. });  
  5. var p2 = new Promise(function(resolve, reject)  
  6. {  
  7.     setTimeout(reject, 100, "rejected - printed");  
  8. });  
  9. Promise.race([p1, p2]).then(function(value)  
  10. {  
  11.     console.log(value); // not get printed  
  12. }, function(reason)  
  13. {  
  14.     console.log(reason); // rejected - printed  
  15.     // p6 is faster, so it rejects  
  16. });  
Load XMLHttpRequestfiles

The use of Promises is by using XMLHttpRequestAPI asynchronously. It loads file in background, example: it will load two JSON files via XHR requests:

automobile.json
  1. {  
  2.     "automobile": [  
  3.     {  
  4.         "vehicle""car",  
  5.         "engine""1200cc"  
  6.     },  
  7.     {  
  8.         "vehicle""bike",  
  9.         "engine""200cc"  
  10.     },  
  11.     {  
  12.         "vehicle""jeep",  
  13.         "engine""2000cc"  
  14.     }]  
  15. }  
employee.json
  1. {  
  2.     "employees": [  
  3.     {  
  4.         "firstName""John",  
  5.         "lastName""Doe"  
  6.     },  
  7.     {  
  8.         "firstName""Anna",  
  9.         "lastName""Smith"  
  10.     },  
  11.     {  
  12.         "firstName""Peter",  
  13.         "lastName""Jones"  
  14.     }]  
  15. }  
Script.js
  1. varary = ['http://localhost/automobile.json''http://localhost/employee.json']  
  2. var p1 = new Promise(function(resolve, reject)  
  3. {  
  4.     letsrcurl = getJSON(ary[0], resolve);  
  5. });  
  6. var p2 = new Promise(function(resolve, reject)  
  7. {  
  8.     letjson = getJSON(ary[1], resolve);  
  9. });  
  10. functiongetJSON(json, resolve)  
  11. {  
  12.     varxmlhttp = new XMLHttpRequest(json);  
  13.     xmlhttp.open("GET", json);  
  14.     xmlhttp.send();  
  15.     xmlhttp.onload = function()  
  16.     {  
  17.         if (xmlhttp.status === 200)  
  18.         {  
  19.             resolve(xmlhttp.responseText);  
  20.         }  
  21.     }  
  22. };  
  23. Promise.all([p1, p2]).then((val) =>  
  24. {  
  25.     document.getElementById('div2').innerHTML = val;  
  26. });  
Output: it will load data of ‘val’ into ‘div2’ of HTML.



Promises Advantages

 

  • It gives us ability to write async code synchronously.
  • You can handle via handler whether its resolved or rejected
  • Solve problem of code pyramids, ex-

 

  1. step1(function(value1)  
  2. {  
  3.     step2(value1, function(value2)  
  4.     {  
  5.         step3(value2, function(value3)  
  6.         {  
  7.             step4(value3, function(value4)  
  8.             {  
  9.                 // Do something with value4  
  10.             });  
  11.         });  
  12.     });  
  13. });  
This is solved and simplified via Promise prototypal methods & chaining.
  1. var p1 = new Promise().resolve('resolve');  
  2. p1.then((value) =>  
  3. {  
  4.     console.log(value);  
  5. }).then((value) =>  
  6. {  
  7.     console.log(value);  
  8. }).then((value) =>  
  9. {  
  10.     console.log(value);  
  11. }).then((value) =>  
  12. {  
  13.     console.log(value);  
  14. });  
Libraries providing Promises

Promises are supported via different libraries Q, AngularJS, BlueBird etc. There are many libraries providing promises as an abstraction on top of JavaScript Native Promises API, e.g.

Q.js
  1. var functionA = function()  
  2. {  
  3.     return "ret A";  
  4. };  
  5. var functionB = function()  
  6. {  
  7.     return "ret B";  
  8. };  
  9. var promise = Q.all([Q.fcall(functionA), Q.fcall(functionB)]);  
  10. promise.spread(finalStep);  
AngularJS
  1. // Simple GET request example:  
  2. $http(  
  3. {  
  4.     method: 'GET',  
  5.     url: '/someUrl'  
  6. }).then(function successCallback(response)  
  7. {  
  8.     // this callback will be called asynchronously  
  9.     // when the response is available  
  10. }, function errorCallback(response)  
  11. {  
  12.     // called asynchronously if an error occurs  
  13.     // or server returns response with an error status.  
  14. });  
It generates a HTTP request and returns a response.

Summary

Please share your feedback / comments.

<< Voice of a Developer: JavaScript Web App Performance Best Practices - Part Thirty One