PKD
PKD

Reputation: 707

How To: correctly chain AngularJS async calls (AngularJs 1.7.5)

Recently started thinking that it was time to do a massive update to my logical operations, and part of that is the proper chaining of Angular JS asynchronous promise calls. Given the following code, how would I re-write it to be a proper chaining of two separate methods? (Yes, I've seen other posts about this, but they all deal with other versions of Angular, or other syntaxes, and I'm looking for something more up-to-date.)

vm.functionName = (
    function() {
        vm.processing = true;

        api.promise1({ Id: vm.Id })
            .then(
                function(result) {
                    if (result.error) {
                        notificationService.danger("<h5>An error occurred.</h5><h6>Details: {0}</h6>".format(result.error));
                    } else {
                        api.promise2({ param: vm.param })
                            .then(
                                function(result2) {
                                    if (result2.error) {
                                        notificationService.danger("<h5>An error occurred.</h5><h6>Details: {0}</h6>".format(result2.error));
                                    } else {
                                        vm.data = result2.data;
                                        notificationService.success("<h5>Operation successful!.</h5>");
                                    }

                                    vm.processing = false;
                                }
                            )
                            .catch(
                                function (err) {
                                    console.error(err);
                                    notificationService.danger("<h5>An error occurred.</h5><h6>Details: {0}</h6>".format(err.statusText));
                                    vm.processing = false;
                                }
                            );
                    }
                }
            )
            .catch(
                function (err) {
                    console.error(err);
                    notificationService.danger("<h5>An error occurred.</h5><h6>Details: {0}</h6>".format(err.statusText));
                    vm.processing = false;
                }
            );
    }
);

Logically, my brain tells me that I should be able to do something like this:

vm.functionName = (
    function() {
        vm.processing = true;

        vm.promise1()
          .then(
              vm.promise2()
                .then(
                    notificationService.success("<h5>Operation successful!.</h5>");
                    vm.processing = false;
                );
            );
          );
    }
);

vm.promise1 = (
    function() {
        api.promise1({ Id: vm.Id })
            .then(
                function(result) {
                    if (result.error) {
                        notificationService.danger("<h5>An error occurred.</h5><h6>Details: {0}</h6>".format(result.error));
                    }
                }
            )
            .catch(
                function (err) {
                    console.error(err);
                    notificationService.danger("<h5>An error occurred.</h5><h6>Details: {0}</h6>".format(err.statusText));
                }
            );
    }
);

vm.promise2 = (
    function() {
        api.promise2({ param: vm.param })
            .then(
                function(result) {
                    if (result.error) {
                        notificationService.danger("<h5>An error occurred.</h5><h6>Details: {0}</h6>".format(result.error));
                    } else {
                        vm.data = result2.data;
                    }
                }
            )
            .catch(
                function (err) {
                    console.error(err);
                    notificationService.danger("<h5>An error occurred.</h5><h6>Details: {0}</h6>".format(err.statusText));
                }
            );
    }
);

Update: the "api...." calls above call to my service.js layer, where methods exist like such:

promise1: function (params, error) {
    return $http
        .post("/C#Controller/Method1", params)
        .then(handleSuccess)
        .catch(function (e) {
            handleError(e, error);
        });
},

promise2: function (params, error) {
    return $http
        .post("/C#Controller/Method2", params)
        .then(handleSuccess)
        .catch(function (e) {
            handleError(e, error);
        });
},

Updated, per Pop-A-Stash's ideas, as now implemented:

//#region Api Calls and Helper
function apiCallOne() {
    return api.promise1({ Id: vm.Id });
}

function apiCallTwo() {
    return  api.promise2({param: vm.param });
}

function handleApiCallError(resultOrError, ngModelToSet) {
    var errMsg = resultOrError.statusText === undefined ? resultOrError.error === undefined ? "Unknown Error" : resultOrError.error : resultOrError.statusText;

    notificationService.danger("<h5>An error occurred.</h5><h6>Details: {0}</h6>".format(errMsg));

    //This allows updating things like variables and ng-model goodies, via an inset function.
    if (ngModelToSet) {
        ngModelToSet();
    }
}
//#endregion

//#region Initialization
function init() {
    vm.pgLoaded = false;

    apiCallOne()
        .then(
            function(result) {
                if (!result.error) {
                    vm.data = result.data;
                    vm.pgLoaded = true;
                } else {
                    handleApiCallError(result, function() { vm.pgLoaded = true; });
                }
            }
        )
        .catch(function(errorOne) { handleApiCallError(errorOne, function() { vm.pgLoaded = true; }); });
}

init();
//#endregion

Upvotes: 0

Views: 219

Answers (2)

OliB
OliB

Reputation: 126

You could shorten your code significantly using recursion to call the next promise in an array of objects containing promises and their parameters using something similar to this:

function runPromises(promises) {
    var first = promises.shift();

    first.function(first.params).then(function(resp) {
        if (promises.length > 1) {
          runPromises(promises);
        } 
    }).catch(function (error) {
        handleError(error);
    });
}

with an initial array of something like this:

  var promises = [
      {
          function: promise1,
          params: any
      },
      {
          function: promise2,
          params: any
      }
    ];

If each promise response requires individual handling you could add a callback to be fired after the promise is resolved for each promise.

Upvotes: 3

Pop-A-Stash
Pop-A-Stash

Reputation: 6652

If you want to chain them in a specific order, then you are already doing it correctly. However I see some code duplication that could be cleaned up:

vm.apiCallOne = apiCallOne;
vm.apiCallTwo = apiCallTwo;
vm.runChainedCalls = runChainedCalls;

function runChainedCalls() {
  vm.processing = true;

  vm.apiCallOne()
  .then(function(result1) {
    if(!result1.error) {
      vm.apiCallTwo().then(function(result2) {
        if(!result2.error) {
          notificationService.success("<h5>Operation successful!.</h5>");
          vm.data = result2.data;
          vm.processing = false;
         }
         else {
           handleError(result2);
         }
      })
      .catch(function(errorTwo) {
         handleError(errorTwo)
      });
    }
    else {
      handleError(result1);
    }
  })
  .catch(function(errorOne) {
    handleError(errorOne);
  });
}

function apiCallOne(){
  return api.callOne(param);  //don't forget to return the promise object
};

function apiCallTwo() {
  return api.callTwo(param);  //don't forget to return the promise object
};

function handleError(resultOrError) {
  notificationService.danger("<h5>An error occurred.</h5><h6>Details: {0}</h6>".format(resultOrError.statusText));
  vm.processing = false;
}

You only need one .then() and .catch() for each call in your controller. Anymore than that is code duplication.

If you want to run them concurrently and don't care about order, you would use the $q.all() function to run them at the same time:

function runConcurrentCalls() {

  $q.all([api.callOne(param), api.callTwo(param)]).then(function(responseArray) {
    // responseArray contains array of both call responses
    console.log(responseArray[0]);
    console.log(responseArray[1]);
  })
  .catch(function() {
    //something went wrong with one or both calls
  });
}

Upvotes: 1

Related Questions