Overview
Are you fed up with using traditional callback-based approaches in asynchronous operations? Are you finding it difficult to handle the errors during the callback process? Are you seeing duplicate callback calls? Well, I have some good news for you!
We have a simpler and alternative way for it and we call it Promise. Sencha introduced Promises in Ext JS 6, it makes our code more modular and simpler as compared to callbacks.
In this article, I am going to talk about following:
- What is a Promise ?
- Why to Use Promise ?
- How to create Promises in Ext JS?
- Deep dive into Promises
- Sample Example
- Advantages of Promises
- Summary
What is a Promise ?
A Promise represents the result of a task or an operation, which may or may not have completed. It is used for asynchronous computations. Promise is natively introduced in the ECMAScript-6. However, even before that there were few JS libraries had their own implementations of it.
At any stage, a Promise will be in one of these 4 states:
- Pending: Initial state, not fulfilled or rejected.
- Fulfilled: operation completed successfully.
- Rejected: operation failed.
- Settled : It has been fulfilled or rejected already.
The following diagram depicts the state transition of a promise:
In the above diagram, we can see, a pending promise can become either fulfilled with a value, or rejected with a reason (error). When either of these happens, the associated handlers queued up by a promise’s then method are called. If the promise has already been fulfilled or rejected when a corresponding handler is attached, the handler will be called, so there is no race condition between an asynchronous operation completing and its handlers being attached.
As the then() and catch() methods return promises, they can be chained.
A promise is said to be settled if it is either fulfilled or rejected, but not pending.
Why to use Promises?
Promises provide a simpler alternative way for executing, composing, and managing async operations when compared to callbacks. Promises provide us a great way of handling asynchronous errors using approaches that are similar to synchronous try/catch.
Asynchronous calls in Ext JS and Sencha Touch can be done in a number of ways, with Store and Ext.Ajax probably being the most common. These have a variety of options for specifying callbacks. For example, Store.load() expects a callback option to be passed. However Ext.Ajax.request typically has success and failure callback functions to be passed.
The point is that regardless of how an asynchronous call is performed, the code that invokes this logic should not be concerned with how it is being done. External code should not be burdened with having to pass either success,failure or callback options. Using a Promise creates a consistent API for your asynchronous logic. If external code always knows a Promise will be returned, it can always work with the asynchronous logic in a similar way.
How to create Promises in Ext JS?
- By Instantiating Ext.Promise
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var promise = new Ext.Promise(function(resolve, reject){ if(expression fulfilled){ resolve(data); } else{ reject(error reason); } }); promise.then( function(value){ ... }, //success callback function(error){ ... }, //reject callback function(value){ ... }, //progress callback Scope ); |
Here, we have created a promise by instantiating Ext.Promise, which will take a callback with resolve and reject arguments. Resolve will fulfill the promise and reject will fail the promise. After that we can attach our callbacks to promise through the then() method, which will take three callbacks called success, fail and progress callbacks. The success callback will be called when promise is fulfilled, fail callback will be called when promise has failed and the progress will be called for progress updates. The then() will return a promise for promise chaining. We will discuss promise chaining in more detail at a later stage.
2. By Instantiating Ext.Deferred
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
function loadEmployees(){ var deferred = new Ext.Deferred(); Ext.Ajax.request({ url : url, success : function(response){ deferred.resolve(response.responseText); }, failure : function(response){ deferred.reject(response.status); } }); return deferred.promise; //will return the underlying promise of deferred } var promise = loadEmployees(); promise.then( function(value){ ... }, //success callback function(error){ ... }, //reject callback function(value){ ... }, //progress callback scope ); |
Deep dive into Promises
1. The Promise Done()
The done() terminates the promise chain and throws un-handled rejections i.e. if have not caught any generated failure then it will ensure that these rejection errors will be thrown.
1 2 3 4 |
promise.then( function(value) { ... } // callback ) .done(); |
2. The Promise Always()
The always() will be invoked whether a promise succeeds/fails. This is like “finally” block in try… catch… finally.
1 2 3 4 5 6 7 |
promise.then( function() { ... } ) .always(function(){ // codes goes here ... }) |
3. Promise Chaining
Chaining promises can make asynchronous code flow synchronously. Promise chaining through then() will provide sequencing.
For example, consider the following sum method which delivers a result using a promise.
1 2 3 4 5 6 7 8 |
sum : function (value) { new Ext.Promise(function(resolve, reject){ setTimeout(() => { resolve(value + 1); }, 0); }); }; |
Imagine you need to invoke the sum method four times, and each time you invoke sum, you need to pass the result from the previous call into the next call (this would simulate HTTP APIs where you need to make multiple requests, all dependent on one another, to fetch all of data required for a page).
With promises, the series of method calls could look like the following.
1 2 3 4 5 6 7 8 9 10 |
sum(1) .then(sum) .then(sum) .then(sum) .then(verify); function verify(result) { console.log(result); done(); }; |
The above code also verifies that the final result is 5, because compute will add 1 to the result on each invocation. The code works because each call to the then method of a promise will result in a new promise.
We saw that we can add multiple then() statement to promise and it will create a chaining process where the return of one then() will be the input for the other then() block; and it will help us for transforming data in each step.We can add multiple then() method’s and each then() will return a promise with the transformed value and the execution of then() will go sequentially.
4. The Promise All()
The Ext.Promise.all(promisesOrValues ) method returns a promise that resolves when all of the promises in the promisesOrValues argument have resolved,or rejects with the reason of the first passed promise that rejects.
1 2 3 4 5 |
var resultingPromise = Ext.Promise.all([promise1, promise2,......]); resultingPromise.then( ... ); |
The Ext.Promise.all() will execute all the augmented promises parallelly and the resulting promise will be executed on the resolve/reject of all the promises.
If all the promises are resolved then it will invoke the resolve/success callback and if any one of these promises is rejected then it will invoke the reject callback of the resulting promise.
5. Error Handling in Promise
- The then() method of promise is taking two arguments. One is success callback and other is the reject callback. One way is by using the reject callback we can capture the generated errors.
1 2 3 4 5 6 |
promise.then( function( value ){ ... }, // success callback function( error ){ // Error callback // Have some error handling codes } ); |
- the otherwise() method can be used for error handling. It accepts reject callback and returns a new promise of the the transformed value. i.e., a Promise that is resolved with the original resolved value, or resolved with the callback return value or rejected with any error thrown by the callback.
1 2 3 4 |
promise.otherwise( function(value){ ... }, // Rejection callback scope ) |
the otherwise() is not a special, it adds sugar to then(undefined, function(){ … });
Sample Example
Let’s take an example of the list of all the achievements of an employee for the year’s 2013, 2014, 2015 and from the service, we can read achievements for a year. Let’s code it from the scratch.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
function getAchievements(year, successCallback, errorCallback){ Ext.Ajax.request({ url : url, params : {year : year}, success : function(response){ successCallback(JSON.parse(response.responseText)); }, failure : function(response){ errorCallback(response.status); } }); } function handleError(){ // handle error } function loadAcheivements(){ getAchievements(2013, function(data1){ getAchievements(2014, function(data2){ getAchievements(2015, function(data3){ var achievements = [].concat(data1, data2, data3); console.log(achievements); // will give a list of all the achievements }, handleError); }, handleError); }, handleError); } |
Here, the callback makes the business logic less readable if we have a complex requirement. The Promise representation will make the code simpler to manage and maintain. Let’s code it by using promise.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
function getAchievements(year){ var deferred = new Ext.Deferred(); Ext.Ajax.request({ url : url, params : {year : year}, success : function(response){ deferred.resolve(JSON.parse(response.responseText)); }, failure : function(response){ deferred.reject(response.status); } }); return deferred.promise; // will return underlying promise } function handleError(){ // handle error } function loadAchievements(){ var achievements = []; getAchievements(2013).then(function(data1){ achievements = achievements.concat(data1); return getAchievements(2014); //will return a promise so "Queueing asynchronous Actions" will take place }) .then(function(data2){ achievements = achievements.concat(data2); return getAchievements(2015); //will return a promise so "Queueing asynchronous Actions" will take place }) .then(function(data3){ achievements = achievements.concat(data3); console.log(achievements); }) .otherwise(handleError) .done(); } |
Promise implementation simplifies the code and makes the code more modular. Let’s make it more simpler by using Ext.Promise.all();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function loadAchievements(){ var promises = [getAchievements(2013), getAchievements(2014), getAchievements(2015)]; Ext.Promise.all(promises).then(function(values){ // the values we be a array //values[0] will be the resolved value of promise 1 //values[1] will be the resolved value of promise 2 //values[2] will be the resolved value of promise 3 var achievements = [].concat(values[0], values[1], values[2]); console.log(achievements); }) .otherwise(handleError) .done(); } |
Advantages of Promises
- Promises are a replacement for callbacks to help us to deal with multiple asynchronous operations.
- Promise will make our code more modular as compared to callbacks.
- One of the biggest (and subtlest) ways promises gain their composability is by the uniform handling of return values and uncaught exceptions.
Summary
In this article, we talked about how to use a Promises in Ext JS and looked into the details of Promises. We can say, Promises are a great alternate for callbacks when we are working with async operations. Also, Promises being composable, it allows you to think in terms of simple, if-then-when kind of logic and the code becomes far more readable and maintainable. With a lot of functionalities being moved to front-end, the exception handling must be rock solid and Promises provide us a great way of handling the same. I have started leveraging this. What about you?
References
- https://docs.sencha.com/extjs/6.0/6.0.2-classic/#!/api/Ext.Promise
- https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise
- https://www.sencha.com/blog/asynchronous-javascript-promises/