一般在WEB开发时总会涉及到从服务器调用接口的操作,为了不影响用户操作,一般会使用异步方式执行,从而不阻塞用户的界面操作。而一些常用的调用服务会封装成独立模块供使用。这时候就会遇到一些问题。比如:若所有服务内的方法都是异步方式的,则如何在外部控制两个异步任务的顺序?如何较为统一地处理超时以及执行异常的状况?…等等
直到后来看到了$q
…
异步操作
异步操作有一般使用 jQuery的ajax,本文结合AngularJs,因此也顺带引入Angular的$http
服务。
ajax
ajax的常用写法如下:
$.ajax({
type:'post',//or 'get'
url:'api/foo',
data:{'a':'v'},
async:true, //or false
success:function(response){
parseResponse(response);
},
error:function(err){
handleError(err);
}
})
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
在上述场景中async
决定了ajax是否阻塞界面进程。若asycn=false,则在请求过程中用户界面无响应,反之无影响。
需要强调的是:若在async=true
情况下更新Angular的$scope中的数据,应该调用$scope.$apply()
来手动完成刷新
$http
基本使用
angular中的$http
默认均为异步执行,同时与$scope
的刷新不冲突。常用方式如下:
/**
* @param method:post,get.....
* @param url:api REST路径
* @param data:封装在request请求中的参数
* @param param:写在url路径后面的参数'?param=v¶m2=v2...'
* @param timeout:超时时间
*/
$http({method:'POST', url:url, data:data, param:param,
timeout:5000})
.success(function(response){
parseResponse(response);
})
.error(function(err,stat){
handleErr(err);
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
[附]post的问题
若直接使用AngularJs 的 $http post
请求在SpringMVC中通过@RequestParams是拿不到值的。
参考angular的post请求,SpringMVC后台接收不到参数值的解决方案将postType从application/json
改成application/x-www-form-urlencoded
即可。
在angular模块中进行配置,如下:
app.config(function($httpProvider) {
$httpProvider.defaults.headers.put['Content-Type'] = 'application/x-www-form-urlencoded';
$httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
// Override $http service's default transformRequest
$httpProvider.defaults.transformRequest = [function(data) {
/**
* The workhorse; converts an object to x-www-form-urlencoded serialization.
* @param {Object} obj
* @return {String}
*/
var param = function(obj) {
var query = '';
var name, value, fullSubName, subName, subValue, innerObj, i;
for (name in obj) {
value = obj[name];
if (value instanceof Array) {
for (i = 0; i < value.length; ++i) {
subValue = value[i];
fullSubName = name + '[' + i + ']';
innerObj = {};
innerObj[fullSubName] = subValue;
query += param(innerObj) + '&';
}
} else if (value instanceof Object) {
for (subName in value) {
subValue = value[subName];
fullSubName = name + '[' + subName + ']';
innerObj = {};
innerObj[fullSubName] = subValue;
query += param(innerObj) + '&';
}
} else if (value !== undefined && value !== null) {
query += encodeURIComponent(name) + '='
+ encodeURIComponent(value) + '&';
}
}
return query.length ? query.substr(0, query.length - 1) : query;
};
return angular.isObject(data) && String(data) !== '[object File]'
? param(data)
: data;
}];
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
直接异步遇到的问题
如果有一个需求需要先获取用户信息(如getUser()
),之后获取用户权限(如getPermission()
)。假设两个方法都是异步的,那在直接调用的时候:
getUser();
getPermission();
- 1
- 2
这样有可能出现在得到用户信息前就调用getPermission()方法,这样逻辑就有问题就出错。但是如果必须要在获得用户信息后申请权限,则要么把getUser()取消异步,这样会影响界面响应。要么:
function getUser(getPermission){
$.ajax({
...
.success(function(user){
getPermission(user);
})
})
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
这样一层一层嵌套两个函数还好,如果在多了foo(), bar()。。。那代码就很难看了。
承诺与延迟
再一次~~~对于$q
的教程,若有梯子的同学,强烈建议看看这个视频 The Power of $q
by Dave Smith at ng-europe 2014
$q的基本使用
$q
主要是在$http
的外围包上一个q承诺(promise),同时把$http结果用q.promise处理,然后直接返回q.promise就好了。
var defered= $q.defer();
$http({method:'POST', url:'api/url', data:{data}, timeout:5000})
.success(function(response){
defered.resolve(response);
})
.error(function(err,status){
if(status === 405){
defered.reject('internal error');
} else if(status === -1){
defered.reject('timeout');
} else {
defered.reject('other error');
}
});
return defered.promise;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
调用时候,直接promise.then()
如下:
service.getPromise().then(
function(result){
//resolve的数据
parseResult(result);
},
function(err){
//reject的错误
handleErr(err);
}
//function(){
// //defered.notify()//一般不常用,可以多次调用notify
// handlePregress();
//}
);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
优势
可以在调用时在决定每个promise的异步或者同步。
方便组织
同时执行多个异步任务的方法如下:
var promises = [promise1, promise2]
$q.all(promises).then(function(responses));
- 1
- 2
或者:
var promises = [promise1, promise2]
$q.all(promises).then(
spread(function(return1, return2){})
)
- 1
- 2
- 3
- 4
这样就可以做到多个promise的结果与函数传入参数的一一对应。
要做到依次执行则使用如下代码:
promise1.then(function(){
promise2.then(functin(){})
})
- 1
- 2
- 3
$q.when()
作为缓存方法
若一个长时间获取资源的方法仅需在第一次执行的时候从服务器获取,下次直接从本地缓存获得,就可以用如下结构:
app.factory('movie', function($q,$http){
var cached, p;
return {
getMovie:function(){
return $q.when(cached || p || helper());
}
};
function helper(){
var defered = $q.defer();
p = defered.promise;
$http({method:'get', url:url})
.success(function(movie){
cached = movie;
defered.resolve(movie);
});
return defered.promise;
}
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
这样的好处在于只有在用户第一次发起请求时才会从服务器获取,在本地有缓存的时候,就直接返回缓存数据。
同时可以在用户在短时间频繁发起请求,而本地还没有缓存时候,避免多次发起服务器请求的情况(这时候返回的认识promise即变量p)。