链接多个可选异步 Ajax 请求

Chaining Multiple Optional Async Ajax Requests

我正在使用 Angular 1.5.8。我应用中的视图需要相同 3 ajax 请求的不同组合。一些视图需要来自所有三个端点的数据,其他视图需要来自两个甚至一个端点的数据。

我正在开发一个管理此数据检索的功能,要求应用程序仅调用每个端点一次。我希望 ajax 请求在需要时被调用 ,但仅在需要时调用 。目前我已经创建了一个有效的函数,但它似乎可以使用改进。

$rootScope 中包含以下函数。它使用 fetchData() 函数根据请求循环获取请求。检索数据时,将其存储在全局变量 $rootScope.appData 中,然后再次调用 fetchData()。当检索到所有数据时,延迟承诺将得到解决,数据将返回到控制器。

$rootScope.appData = {};

$rootScope.loadAppData = function(fetch) {
  var deferred = $q.defer();

  function getUser() {
    $http
      .get('https://example.com/api/getUser')
      .success(function(result){
        $rootScope.appData.currentUser = result;
        fetchData();
      });
  }

  function getPricing() {
    $http
      .get('https://example.com/api/getPricing')
      .success(function(result) {
        $rootScope.appData.pricing = result;
        fetchData();
      });
  }

  function getBilling() {
     $http
       .get('https://example.com/api/getBilling')
       .success(function(result) {
         $rootScope.appData.billing = result;
         fetchData();
       });
  }

  function fetchData() {
    if (fetch.user && !$rootScope.appData.currentUser) {
      getUser();
    } else if (fetch.pricing && !$rootScope.appData.pricing) {
      getPricing();
    } else if (fetch.billing && !$rootScope.appData.billing) {
      getBilling();
    } else {
      deferred.resolve($rootScope.appData);
    }
  }

  if ($rootScope.appData.currentUser && $rootScope.appData.pricing &&$rootScope.appData.billing) {
    deferred.resolve($rootScope.appData);
  } else {
    fetchData();
  }

  return deferred.promise;
};

一个对象fetch作为一个属性被提交,这个对象显示了ajax请求调用哪个。调用 $rootScope.loadAppData() 的示例,其中仅请求 userpricing 数据,如下所示:

$rootScope.loadAppData({user: true, pricing: true}).then(function(data){
   //execute view logic. 
});

我在想:

  1. 这些函数的链接是否应该以不同的方式进行? fetchData() 功能是否足够,或者这是执行此功能的一种奇怪方式?
  2. 有没有办法同时调用所有需要的 Ajax 请求,但在解决承诺之前等待所有需要的调用完成?
  3. $rootScope中存储这样的数据是不是很不正常?

我知道此功能当前无法正确处理错误。这是我将在使用此代码段之前添加的功能,但与我的问题无关。

不使用 .success 方法,而是使用 .then 方法和 return 数据到它的成功处理程序:

function getUserPromise() {
    var promise = $http
      .get('https://example.com/api/getUser')
      .then( function successHandler(result) {
          //return data for chaining
          return result.data;
      });
    return promise;
}

使用服务代替 $rootScope:

app.service("myService", function($q, $http) {

    this.loadAppData = function(fetchOptions) {

        //Create first promise
        var promise = $q.when({});

        //Chain from promise
        var p2 = promise.then(function(appData) {
            if (!fetchOptions.user) {
                return appData;
            } else {
                var derivedPromise = getUserPromise()
                  .then(function(user) {
                    appData.user = user;
                    //return data for chaining
                    return appData;
                });
                return derivedPromise;
            );
        });

        //chain from p2
        var p3 = p2.then(function(appData) {
            if (!fetchOptions.pricing) {
                return appData;
            } else {
                var derivedPromise = getPricingPromise()
                  .then(function(pricing) {
                    appData.pricing = pricing;
                    //return data for chaining
                    return appData;
                });
                return derivedPromise;
            );
        });
        //chain from p3
        var p4 = p3.then(function(appData) {
            if (!fetchOptions.billing) {
                return appData;
            } else {
                var derivedPromise = getBillingPromise()
                  .then(function(user) {
                    appData.billing = billing;
                    //return data for chaining
                    return appData;
                });
                return derivedPromise;
            );
        });

        //return final promise
        return p4;
    }
});

上面的例子为一个空对象创建了一个承诺。然后它链接三个操作。每个操作都会检查是否需要提取。如果需要,执行一次提取并将结果附加到 appData 对象;如果不需要提取,appData 对象将传递到链中的下一个操作。

用法:

myService.loadAppData({user: true, pricing: true})
  .then(function(appData){
    //execute view logic. 
}).catch(functon rejectHandler(errorResponse) {
    console.log(errorResponse);
    throw errorResponse;
});

如果任何提取操作失败,将跳过链中的后续操作并调用最终的拒绝处理程序。

因为调用承诺的 .then 方法 return 是一个新的派生承诺,所以很容易创建承诺链。可以创建任意长度的链,并且由于一个 promise 可以用另一个 promise 解决(这将进一步推迟其解决),因此可以 pause/defer 在链中的任何点解决 promise。这使得实现强大的 API 成为可能。 -- AngularJS $q Service API Reference - Chaining Promises

在原文post中找到了很好的方法来回答问题2。使用 $q.all() 允许承诺同时执行,一旦它们全部完成就解决,或者一旦其中一个失败就失败。感谢@georgeawg,我已将此逻辑添加到服务中。这是我将此代码放入服务中的重写,并且 运行 同时调用所有:

  services.factory('appData', function($http, $q) {
    var appData = {};
    var coreData = {};

    appData.loadAppData = function(fetch) {
      var deferred = $q.defer();
      var getUser = $q.defer();
      var getPricing = $q.defer();
      var getBilling = $q.defer();

      if (!fetch.user || coreData.currentUser) {
        getUser.resolve();
      } else {
        $http
          .get('https://example.com/api/getUser')
          .success(function(result){
            coreData.currentUser = result;
            getUser.resolve();
          }).error(function(reason) {
            getUser.reject(reason);
          });
      }

      if (!fetch.billing || coreData.billing) {
        getBilling.resolve();
      } else {
         $http
           .get('https://example.com/api/getBilling')
           .success(function(result) {
             coreData.billing = result;
             getBilling.resolve();
           }).error(function(reason) {
             getBilling.reject(reason);
           });
      }

      if (!fetch.pricing || coreData.pricing) {
        getPricing.resolve();
      } else {
         $http
           .get('https://example.com/api/getPricing')
           .success(function(result) {
             coreData.pricing = result;
             getPricing.resolve();
           }).error(function(reason) {
             getPricing.reject(reason);
           });
      }

      $q.all([getPricing.promise, getUser.promise, getBilling.promise]).then(function(result) {
        deferred.resolve(coreData);
      }, function(reason){
        deferred.reject(reason);
      });

      return deferred.promise;
    };

    return appData;
  });