【问题标题】:Retry request with $http interceptor使用 $http 拦截器重试请求
【发布时间】:2015-08-12 20:57:43
【问题描述】:

我确信有一种简单的方法可以做我想做的事,但我就是想不通。如果请求失败,如何以角度获取 http 拦截器以重试请求?我想我必须在请求中建立某种承诺,对吗?然后在响应中,我必须检查响应是否是错误的,如果是,那么承诺吗?这是怎么做的?我一直在尝试调整这里的示例:http://docs.angularjs.org/api/ng.$http

我尝试使用拦截器的原因是因为我需要在请求 url 中添加令牌和其他一些东西,以及一些处理 xdr 的东西。

【问题讨论】:

    标签: javascript angularjs


    【解决方案1】:

    这是一个 $http 拦截器,它(立即)重播超时请求(即响应状态 0)。构建这个例子有两个不明显的(对我来说!)元素:

    1. 如何从拦截器中调用 $http - 简单地将 $http 添加到依赖项列表中不起作用,因为角度抱怨循环依赖项
    2. 如何从响应对象中引用原始请求以重试它

    This answer 涉及这两个主题,但涉及更多,因此我在下面提供了一个简化版本。

    joinApp.factory('httpResponseErrorInterceptor',function($q, $injector) {
      return {
        'responseError': function(response) {
          if (response.status === 0) {
            // should retry
            var $http = $injector.get('$http');
            return $http(response.config);
          }
          // give up
          return $q.reject(response);
        }
      };
    });
    
    joinApp.config(function($httpProvider) {
      $httpProvider.interceptors.push('httpResponseErrorInterceptor');
    });
    

    在您的实际实现中,您可能需要更复杂的 http 响应代码处理、重试次数限制等。

    【讨论】:

    • 我添加了一个延迟重试的单独答案,以防有人在思考最简单的方法:stackoverflow.com/a/31965945/1338062
    • 请注意,当移动设备处于离线(飞行)模式时,响应状态可以是0。这将创建一个无限循环的请求,直到设备上线。
    • 如何实现最大重试次数?
    • 这段代码是否会“返回$http(response.config);”真的点击了控制器的回调?
    • 我能想到的唯一限制拦截器重试次数的方法是在请求 url 中添加一个名称间隔的参数,例如?like my_app_retries=1,并增加该参数的值在拦截器中。当然,您尝试访问的资源可能不像请求中的随机任意参数
    【解决方案2】:

    为了完整起见,这里是mygzi's answer 的一个版本,带有重试延迟:

    .factory('httpResponseErrorInterceptor', ['$injector', '$q', '$timeout', function($injector, $q, $timeout) {
      return {
        'responseError': function(response) {
          if (response.status === 0) {
            return $timeout(function() {
              var $http = $injector.get('$http');
              return $http(response.config);
            }, 15000);
          }
          return $q.reject(response);
        }
      };
    }])
    
    .config(function($httpProvider) {
      $httpProvider.interceptors.push('httpResponseErrorInterceptor');
    });
    

    $timeout 返回一个用函数参数返回的内容完成的承诺,因此我们可以方便地返回包装在 $timeout 中的 $http 调用。

    【讨论】:

    • 感谢您提供简单明了的解决方案。
    • Tnx,我真的需要回报的承诺!
    【解决方案3】:

    我已经为我之前编写的应用程序做过这个。要进行重试,将 $http 用法包装在一个函数中(或者最好在一个服务中,以便您可以轻松地重用它)。如果请求失败,请再次调用该函数。

    诀窍是把 promise 对象和每个请求一起传递。如果您为每个请求创建一个新的承诺,那么它将与您返回给原始调用者的那个不匹配,因此一旦请求通过,原始调用者将无法获得他的承诺解决。

    所以是这样的(注意每次重试都会传递defer对象):

    app.service('HttpService', ['$q', function($q) {
      this.makeRequest = _makeRequest;
    
      function _makeRequest(url, data, deffered) {
        // We want to keep the same promise for each request, so we don't loose track
        if (deferred === undefined) {
          deferred = $q.defer();
        }
    
        // Now make the request
        $http({...}).success(...).error(
          function(){
            // If some condition
            _makeRequest(url, data, deffered);
          }
        )
    
        // Lastly return the promise
        return deferred.promise;
      }
    }])
    

    【讨论】:

    • 我将如何调整以便只重试一次?
    • 我想把它放在拦截器中的原因是它会自动发生。我不想到处都做 $http({..}).error(...) 。你知道我的意思吗?
    • 只需添加第四个参数,例如“times_retried”并在重试之前检查它。
    • 我没有尝试使用拦截器,所以我不确定它是如何工作的。但这就是将其作为服务来做的重点,我从不使用 $http,我只是使用 HttpService 代替。当然,我是在项目开始时构建的,所以我从一开始就知道要使用该服务。如果您要更改可能不是那么有趣的大型代码库。
    • 是的,这是一个涉及多个应用程序的模块...为您的时间+1。
    猜你喜欢
    • 2015-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-28
    相关资源
    最近更新 更多