【问题标题】:Chaining Multiple Optional Async Ajax Requests链接多个可选的异步 Ajax 请求
【发布时间】:2016-12-05 18:59:45
【问题描述】:

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

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

$rootScope 中包含以下函数。它使用fetchData() 函数根据请求循环获取请求。检索数据时,将其存储在全局变量 $rootScope.appData 中,然后再次调用 fetchData()。当所有数据都被检索到时,deferred Promise 被解析并且数据被返回给控制器。

$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 中存储这样的数据是否异常?

我知道这个函数目前没有正确处理错误。这是我将在使用此 sn-p 之前添加的功能,但与我的问题无关。

【问题讨论】:

    标签: javascript angularjs asynchronous promise q


    【解决方案1】:

    不使用.success 方法,而是使用.then 方法并将数据返回 到其成功处理程序:

    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;
        }
    });
    

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

    用法:

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

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

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

    【讨论】:

    • 感谢您的回答。在阅读了您的答案/进行了更多研究之后,对我来说很明显这个逻辑应该包含在服务中。我现在已经在我的项目中做出了改变。您是否知道一种方法可以并行而不是按顺序执行所有必需的调用,然后在耗时最长的调用完成时返回结果?
    • 使用 $q.all 并行执行 XHR。当下一个 XHR 依赖于前一个 XHR 的信息时,顺序方法很有用。例如,在检索用户位置后计算运费。
    【解决方案2】:

    在原帖中找到了回答问题 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;
      });
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-06-11
    • 1970-01-01
    • 1970-01-01
    • 2015-01-17
    • 1970-01-01
    • 2023-04-01
    • 2012-05-26
    • 2018-04-10
    相关资源
    最近更新 更多