📜 ⬆️ ⬇️

Proper use of promise in angular.js

image In the process of using angular.js, it is difficult to do without the $ q object (it is promise / deferred), because it is the basis of the whole framework. Deferred mechanism is a very simple and powerful tool that allows you to write concise code. But in order to truly use this power, you need to know about all the possibilities of this tool.
Here are a few things you probably didn't know about.




1. then always returns a new promise.


Take a look at an example:

function asyncFunction() { var deferred = $q.defer(); doSomethingAsync().then(function(res) { res = asyncManipulate(res); deferred.resolve(res); }, function(err) { deferred.reject(err); }); return deferred.promise; } 

Here a new promise $q.defer() is meaningless. The author of the code clearly did not know that then so would return the promise. To improve the code, simply return the result to then:
')
 function asyncFunction() { return doSomethingAsync().then(function(res) { return asyncManipulate(res); }); } 


2. The promise "not lost" result


Again an example:

 function asyncFunction() { return doSomethingAsync().then(function(res) { return asyncManipulate(res); }, function(err) { return $q.reject(err); }); } 

Any result of executing the doSomethingAsync function, either resolve or reject, will “pop up” until it finds its handler (if the handler exists at all). This means that if there is no need to process the result, then you can simply omit the corresponding handler, because the result will not disappear anywhere, it will simply pass on. In this example, the second handler can be safely removed (reject processing), since no manipulations are performed:

 function asyncFunction() { return doSomethingAsync().then(function(res) { return asyncManipulate(res); }); } 

You can also omit the resolve processing if you only need to handle the reject case:

 function asyncFunction() { return doSomethingAsync().then(null, function(err) { return errorHandler(err); }); } 

By the way, for such a case there is syntactic sugar:

 function asyncFunction() { return doSomethingAsync().catch(function(err) { return errorHandler(err); }); } 


3. You can get into the reject handler only by returning $ q.reject ()


Code:

 asyncFunction().then(function (res) { // some code return res; }, function (res) { // some code }).then(function (res) { console.log('in resolve'); }, function (res) { console.log('in reject'); }); 

In this example, no matter how the asyncFunction function asyncFunction , we will see 'in resolve' in the console. This is because there is only one way to be in the reject handler - to return $ q.reject (). In any other cases, the resolve handler will be called. We will rewrite the code so that we can see in the asyncFunction console in the console if asyncFunction returns a reject:

 asyncFunction().then(function (res) { // some code return res; }, function (res) { // some code return $q.reject(res); }).then(function (res) { console.log('in resolve'); }, function (res) { console.log('in reject'); }); 


4. finally does not change promise result



 asyncFunction().then(function (res) { importantFunction(); return res; }, function (err) { importantFunction(); return $q.reject(err); }).then(function (res) { // some resolve code }, function (err) { // some reject code }) 

If you need to execute code regardless of the promise result, use a finally handler that is always called. The finally block does not affect further processing, since it does not change the type of promise of the result. Improving:

 asyncFunction().finally(function () { importantFunction(); }).then(function (res) { // some resolve code }, function (err) { // some reject code }) 

If the finally handler returns $ q.reject (), then the reject handler will be called next. There is no way to call the resolve handler.

5. $ q.all performs functions in parallel


Consider nested call chains:

 loadSomeInfo().then(function(something) { loadAnotherInfo().then(function(another) { doSomethingOnThem(something, another); }); }); 

The doSomethingOnThem functions require the result of executing both loadSomeInfo and loadAnotherInfo . And it does not matter in what order they will be called, it is only important that the doSomethingOnThem function be called after the result from both functions is obtained. This means that these functions can be called in parallel. But the author of this code clearly did not know about the $ q.all method. Rewrite:

 $q.all([loadSomeInfo(), loadAnotherInfo()]).then(function (results) { doSomethingOnThem(results[0], results[1]); }); 

$ q.all accepts an array of functions that will be run in parallel. The promise returned by $ q.all will be called when all functions in the array have completed. The result will be available in the form of an array of results , in which are the results of all functions respectively.
Thus, the $ q.all method should be used in cases when it is necessary to synchronize the execution of asynchronous functions.

6. $ q.when turns everything into promise


There are situations where the code may depend on an asynchronous function, and may depend on synchronous. And then you create a wrapper over the synchronous function to keep order in the code:

 var promise; if (isAsync){ promise = asyncFunction(); } else { var localPromise = $q.defer(); promise = localPromise.promise; localPromise.resolve(42); } promise.then(function (res) { // some code }); 

There is nothing wrong with this code. But there is a way to make it cleaner:

 $q.when(isAsync? asyncFunction(): 42).then(function (res) { // some code }); 

$ q.when a kind of proxy function that accepts either a promise or a normal value, and always returns a promise.

7. Proper error handling in promise


Let's look at an example of error handling in an asynchronous function:

 function asyncFunction(){ return $timeout(function meAsynk(){ throw new Error('error in meAsynk'); }, 1); } try{ asyncFunction(); } catch(err){ errorHandler(err); } 

Do you see a problem here? The try / catch block will only catch errors that occur when executing the asyncFunction function. But, after $timeout launches its callback function meAsynk , any errors that occur there will fall into the application's uncaught exception handler. Accordingly, our catch handler will not know anything.
Therefore wrapping asynchronous functions in try / catch is useless. But what to do in such situations? To do this, asynchronous functions must have a special callback for error handling. In $ q, such a handler is the reject handler.
Rewrite the code so that the error is in the handler (use the catch sugar described above):

 function asyncFunction(){ return $timeout(function meAsynk(){ throw new Error('error in meAsynk'); }, 1); } asyncFunction().catch(function (err) { errorHandler(err); }); 

Consider another example:

 function asyncFunction() { var promise = doSomethingAsync(); promise.then(function() { return somethingAsyncAgain(); }); return promise; } 

This code has one problem: if the function somethingAsyncAgain returns a reject (and as we already know, the reject is also called in cases where errors are falling), the code that caused our function will never know about it. Promises must be consistent, each following should depend on the previous one. But in this example, the promise is broken. To fix the rewrite so:

 function asyncFunction() { return doSomethingAsync().then(function() { return somethingAsyncAgain(); }); } 

Now the code that calls our function completely depends on the result of the execution of the function somethingAsyncAgain , and all errors can be handled by the parent code.

Let's look at this example:

 asyncFunction().then( function() { return somethingElseAsync(); }, function(err) { errorHandler(err); }); 

It would seem that this time everything is correct. But if the error falls in the somethingElseAsync function, it will not be handled by anyone. Let's rewrite the code so that the reject handler is detached:

 asyncFunction().then(function() { return somethingElseAsync(); }).catch(function(err) { errorHandler(err); }); 

Now, any error that occurs will be processed.

PS


The $ q service is an implementation of the Promises / A + standard. For a deeper understanding, I recommend reading this standard.
It is also worth noting that the promise implementation in jQuery differs from the Promises / A + standard. Those who are interested in these differences can read this article .

Source: https://habr.com/ru/post/221111/


All Articles