Proper use of promise in angular.js

    imageIn the process of using angular.js it is difficult to do without the $ q object (it is also a promise / deferred), because it lies at the heart 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 features of this tool.
    Here are a few points you may not have known 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; 
    }
    

    There is no point in creating a new promise $q.defer(). The author of the code clearly did not know that then return a promise. To improve the code, simply return the then result:

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


    2. The result of the promise “is not lost”


    Again an example:

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

    Any result of the function execution doSomethingAsync, whether resolve or reject, will “pop up” until it finds its own 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 just goes on. In this example, you can safely remove the second handler (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 want to handle only 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 ()


    The 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 function completes asyncFunction, in the console we will see 'in resolve'. This is because there is only one way to end up in the reject handler - return $ q.reject (). In any other cases, the resolve handler will be called. We rewrite the code so that we see in the console 'in reject' if it asyncFunctionreturns 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 the result of the promise



    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 the code regardless of the result of the promise, use the finally handler, which is always invoked. Also, 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 guarantee a call to the resolve handler.

    5. $ q.all performs functions in parallel


    Consider the nested call chains:

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

    A function doSomethingOnThemrequires the result of performing both functions loadSomeInfoand loadAnotherInfo. And it does not matter in what order they will be called, it is only important that the function is doSomethingOnThemcalled after the result from both functions is received. 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. We rewrite:

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

    $ q.all accepts an array of functions to be run in parallel. The promise returned by $ q.all will be called when all functions in the array are complete. The result will be available as an array resultsin which the results of all functions are located, respectively.
    Therefore, the $ q.all method should be used in cases where it is necessary to synchronize the execution of asynchronous functions.

    6. $ q.when turns everything into a promise


    There are situations when the code may depend on the asynchronous function, and may depend on the synchronous one. 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 is a kind of proxy function that accepts either promise or a normal value, and always returns promise.

    7. Correct 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? try / catch block will catch only those errors that occur during the execution of the function asyncFunction. But, after it $timeoutstarts its callback function meAsynk, all errors that arise there will fall into the handler of uncaught application errors (application's uncaught exception handler). Accordingly, our catch handler does not recognize 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 to handle errors. In $ q, such a handler is the reject handler.
    We’ll redo the code so that the error appears in the handler (we use the sugar described above catch):

    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 reject (and as we already know, reject is also called in cases when errors occur), then the code that called our function will never know about it. Promises should be consistent, each following should depend on the previous one. But in this example, the promise is broken. To fix it, we rewrite it like this:

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

    Now the code calling our function depends entirely on the result of the function execution somethingAsyncAgain, and all errors can be processed by the higher 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 into the function somethingElseAsync, then it will not be processed by anyone. We rewrite the code so that the reject handler is isolated:

    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 is different from the Promises / A + standard. Those who are interested in these differences can familiarize themselves with this article .

    Also popular now: