在Angular中,取消请求相比jQuery来的更复杂一点,但在开发过程中,有这样一个问题,如果同一个页面多屏切换时公用的同一个接口且是同一个回调函数,在网络或服务器响应不理想的情况下,有可能出现先发的请求后响应的问题,这样一来新一屏页面就有可能显示的是上一屏的数据,这样就很容易出现问题,那么在Angular中如果终止请求呢?
复杂点
不同于jQuery或未经封装的XMLHttpRequest对象,Angular并没有将abort方向暴露出来,但是它提供了一种间接的方式终止活跃的请求,你需要定义一个promise对象,然后调用其resolve来终止请求。
注意:在Angular1.2后,$http对象config参数的timeout属性支持一个特定的毫秒级数值或Promise,在这之前,timeout属性仅支持毫秒级数值,意味着在那之前,没有办法手动终止请求
使用
- 创建一个defer
- 设置timeout属性为defer.promise
- 原理:如果在请求完成之前,调用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();
});