Better

Ethan的博客,欢迎访问交流

Angular中abort后台请求

在Angular中,取消请求相比jQuery来的更复杂一点,但在开发过程中,有这样一个问题,如果同一个页面多屏切换时公用的同一个接口且是同一个回调函数,在网络或服务器响应不理想的情况下,有可能出现先发的请求后响应的问题,这样一来新一屏页面就有可能显示的是上一屏的数据,这样就很容易出现问题,那么在Angular中如果终止请求呢?

复杂点

不同于jQuery或未经封装的XMLHttpRequest对象,Angular并没有将abort方向暴露出来,但是它提供了一种间接的方式终止活跃的请求,你需要定义一个promise对象,然后调用其resolve来终止请求。

注意:在Angular1.2后,$http对象config参数的timeout属性支持一个特定的毫秒级数值或Promise,在这之前,timeout属性仅支持毫秒级数值,意味着在那之前,没有办法手动终止请求

使用

  1. 创建一个defer
  2. 设置timeout属性为defer.promise
  3. 原理:如果在请求完成之前,调用resolve方法,此时就会cancle请求

直接上代码,封装了$http请求,如果同一个请求重复,前一个没完成后一个发出时会将前一个cancle掉

.factory('HttpXhr', ["$http", "$timeout", "$q", 
    function ($http, $timeout, $q) {

      var send = function (config) {
        config.method == 'post' && (config.data = config.data || {});
        var http = $http(config);
        http.catch(function (error) {
          // todo:错误提示与上报
        });
        http.finally(function () {
        });
        return http;
      };

      var defers = {}

      function getDefer(path) {
        if (defers[path]) {
          defers[path].resolve({data: {}});
          // 已手动cancle或请求完成,取消定时器,避免浪费性能
          defers[path].timeoutId && $timeout.cancel(defers[path].timeoutId);
        }
        defers[path] = $q.defer();
        // 设置15s自动超时
        (function (param) {
          defers[path].timeoutId = $timeout(function () {
            defers[param].resolve({data: {}});
            // 防止对象持续增大?
            delete defers[param];
          }, 15000);
        })(path);
        return defers[path];
      }

      return {
        getData: function (path, data, loading, headers) {
          if (loading instanceof Object) {
            headers = loading;
            loading = false;
          }
          var defer = getDefer(path);
          var requestUrl = 'common url';
          var config = {
            url: requestUrl + path,
            data: data,
            method: 'post',
            timeout: defer.promise,
            loading: loading || false
          }
          if (headers) {
            config.headers = headers;
          }
          send(config).success(function (data) {
            defer.resolve(data);
            // 已手动cancle或请求完成,取消定时器,避免浪费性能
            defers[path].timeoutId && $timeout.cancel(defers[path].timeoutId);
            delete defers[path];
          }).error(function (error) {
            defer.reject(error);
          });
          return defer.promise;
        }
      }
    }])

超时时间

我们用Promise设置了timeout属性,可是我们依旧想设置一个最大的具体的数值超时时间怎么办呢,其实也是有办法的,代码在上面已经给出了,直接使用$timeout定时器,在指定时间出发resovle即可。

function getDefer(path) {
    if (defers[path]) {
      defers[path].resolve({data: {}});
      // 已手动cancle或请求完成,取消定时器,避免浪费性能
      defers[path].timeoutId && $timeout.cancel(defers[path].timeoutId);
    }
    defers[path] = $q.defer();
    // 设置15s自动超时
    (function (param) {
      defers[path].timeoutId = $timeout(function () {
        defers[param].resolve({data: {}});
        delete defers[param];
      }, 15000);
    })(path);
    return defers[path];
}

在超时代码里,我使用了一个闭包,因为我们要读取path值,保留当时当前上下文的AO在内存中。同时需要注意的是,如果请求完成或已经cancle了,则取消定时器,避免资源浪费。

加载动画

对于一些比较耗时的请求,合理的做法是设置一个loading动画,在这里貌似我只设置了值,但并没有具体用到,但是$http本身支持loading属性?答案是否定的,为了避免造成误解,在这里拧出来额外说明一下。

主要用到了$httpProvider设置,配合事件使用。

.config(['$httpProvider', function ($httpProvider) {
    $httpProvider.interceptors.push(function ($rootScope) {
      return {
        request: function (config) {
          if (!!config.loading) {
            $rootScope.$broadcast('loading:show');
          }
          return config;
        },
        response: function (response) {
          if (!!response.config.loading) {
            $rootScope.$broadcast('loading:hide');
          }
          return response;
        }
      }
    });
}])

$rootScope.$on('loading:show', function () {
    $ionicLoading.show({
      template: '<ion-spinner icon="bubbles" class="spinner-balanced"></ion-spinner>',
      delay: 200
    })
});
$rootScope.$on('loading:hide', function () {
    $ionicLoading.hide();
});

资料



留言