在上一篇文章里深入的了解了generator
的异常处理以及函数间的调用,这篇文章主要说说上一篇遗留的一些问题
- 异步方法的处理
此篇文章参考老外的一篇文章: 传送门:)
下面是Generator系列的相关文章链接
第一种方案
利用generator
来调用异步主要是为了解决异常调用嵌套的问题,下面先模拟一个嵌套调用的异步过程.
functin request(delay, callback){
setTimeout(function(){
callback({
name: 'feenan'
})
}, +delay);
}
request(1000, function(msg){
console.log(msg);
request(2000, function(msg){
console.log(msg);
})
})
然后我们来看看怎么用generator
来同步
处理这里请求,首先我们来定义生成器函数.
function* main(){
var result = yield request(1000);
console.log(result);
var result1 = yield request(2000);
console.log(result);
}
var it = main();
此时我们需要改进下request
函数.
functin request(delay){
setTimeout(function(){
it.next({
name: 'feenan'
})
}, +delay);
}
注意上面的异步函数里增加了it.next
,这样就能保证回调的值能传入main
函数里去.因为next
函数的参数是上一次yield
表达式的值,第一次给next
传参默认会忽略.
上面的方案虽然实现了异步方法的扁平化处理,但是有些缺点
- 异步方法深度依赖生成器实例,倒致不能重用
- 不能处理多个异步同时调用的情况
来看看第二种方案是怎么解决这种问题的?
第二种方案promise
虽然上面的方案可以实现异步调用的扁平化处理,但是仍然存在问题,下面我们来用Promise
来实现一个更好的扁平化方案
Promise
是es6
提供的原生支持promise规范
的类,提供有then
,all
,catch
方法.我们先来改装下request
方法
function request(id, delay){
return new Promise(function(resolve, reject){
setTimeout(function(){
if(id == '1') reject(new Error('new Error!'));
resolve({
name: 'feenan',
id: id
});
}, +delay);
});
}
Promise
通过new
一个实例,传入待执行的异步方法,内部利用resolve
,reject
来导出成功和失败的信息.可以看出这个方法跟generator
是完全没关系的,已经解藕了,而且也可以重用在别的类库里,然后我们再来定义我们的生成器函数.
function* main(){
try{
var result = yield request('1', 1000);
if(result.name == 'feenan'){
var result1 = yield request('2', 2000);
}
return 'done!';
}catch(err){
console.log(err);
}
}
此处的try...catch
是用来捕获里面异步方法的异常的,通过throw
方法,这个等会儿会讲到.
也许大家会看到,上面的main
方法的yield
会返回一个promise
对象,但是result
应该会接收到异步回调结果的,这里我们应该怎么处理呢?对,我们还需要定义一个运行生成器函数的函数.
function runGenerator(g){
return new Promise(function(resolve, reject){
var it = g(), ret;
(function iterate(val){
ret = it.next(val);
if(!ret.done){
// 检查是否是promise对象
if('then' in ret.value){
ret.value.then(iterate).catch(function(err){
it.throw(err);
});
}else{
setTimeout(function(){
iterate(ret.value);
}, 0)
}
}else{
resolve(ret);
}
})();
});
}
代码看起来有点复杂,我一一来解释.
首先函数本身返回一个promise
对象用来处理生成器函数返回值,内部定义了一个iterate
立即执行函数,runGenerator
内部会先生成一个迭代器名为it
,参数即是生成器函数,然后iterate
会先调用it.next
传递参数,默认第一次参数会忽略,然后检查it.next
的返回值,这个返回值属性格式默认为{value: , done: }
,value
为yield
后表达式的返回值,done
代表迭代是否完毕,因为yield
后面返回的是一个promise
对象,所以检查ret.value
是否是一个promise
对象,是的话则调用then
方法传递当前iterate
本身,这就是运行函数的奥妙所在,因为此时会把异步回调的结果传递给下一次的it.next
,这样main
函数里的result
就能接收到异步回调的结果了,然后就是不断重复上面的步骤直到执行完毕,利用resolve
向外抛出生成器函数的返回值,我们来看看运行的样子
runGenerator(main).then(msg){
console.log(msg); // => done!
}
另外我们还可以利用promise.all
方法达到异步同时调用多个方法的功能,此处只需要定义一个多个异步调用方法,promise.all
会调用多个promise
对象最终只返回一个promise
对象
// 包装多个异常请求成功之后返回一个新的promise
// 利用Promise.all类方法
function requestAll(){
var promises = [1000, 2000, 3000].map(function(v, k){
return new Promise(function(resolve, reject){
setTimeout(function(){
resolve({
name: 'feenan',
id: v
})
}, v);
});
});
return Promise.all(promises);
}
然后我们修改main
函数里的request
即可
// var result = yield request('1', 1000);
var result = yield requestAll();
此处需要注意下,上面的result
是一个数组,里面每项为单个promise
对象的返回值
可以看到利用promise
+generator
可以完美的原生实现异步调用扁平化,这对复杂业务逻辑的实现是相当好的.
下面我再说说es7
里对于异步调用方法的更进一步的完美支持,而且代码量更少
第三种方案async
es7
里提供了async
语法来定义异步函数,配合await
关键字轻易就能实现上面的功能
function request(id, delay){
return new Promise(function(resolve, reject){
setTimeout(function(){
if(id == '1') reject(new Error('new Error!'));
resolve({
name: 'feenan',
id: id
});
}, +delay);
});
}
async function main() {
var result1 = await request('1', 1000);
console.log(result1);
var result2 = await request('2', 2000);
console.log(result2);
console.log( "The value you asked for: " + resp.value );
}
main();
await
告诉引擎需要等后面的异步执行完成之后才能执行下面的语句,这是原生支持的,不需要像上面那样定义运行函数来一步一步执行.
await
后面必须是一个promise
对象实例,所以这里也的用上promise
特性.
不过async
语法成为标准之路还很遥远,庆幸的是traceur
支持这个语法.
总结
最后我觉的还是promise
和generator
是完美解决异步调用的方案,以上所有实例都可以通过traceur
命令来运行.