三个异步函数,如:
function async1(param, callback){
setTimeout(function(){
callback(param+'->async1');
},2000);
}
function async2(param, callback){
setTimeout(function(){
callback(param+'->async2');
},2000);
}
function async3(param, callback){
setTimeout(function(){
callback(param+'->async3');
},2000);
}
一般的调用方式如下:
async1('test', function(result1){
console.log('异步1结果:'+result1);
async2(result1, function(result2){
console.log('异步2结果:'+result2);
async3(result2, function(result3){
console.log('异步3结果:'+result3);
});
});
});
可以想象,当回调层次更多,再混入复杂的业务逻辑时,是多么痛苦。
promise,即承诺对象,是以前常用的方案,promise可以将嵌套转化为链式调用,可谓是极大的进步了。
首先定义一个很关键的转化函数,函数的作用是将普通异步函数转化为promise:
var promiseHelper = function(asyncFun){
return function(){ // 注意,这个函数即转化后的新函数,调用时与原函数区别是少最后一个入参(即不传回调函数),函数内会自己定义一个函数放到参数列表最后作为原异步函数的回调函数
var args = [].slice.call(arguments);
var func; // 很关键,这个函数在promise构造时生成,并在异步完成时执行,执行后完成promise并传入结果
args.push(function(){ // 这个函数充当原异步函数的回调函数
if(func) func.apply(null, arguments);
})
asyncFun.apply(null, args); // 执行原异步函数
return new Promise(function(resolve, reject){
func = function(){
resolve.apply(null, arguments);
}
});
}
}
然后就很简单了:
var promise1 = promiseHelper(async1);
var promise2 = promiseHelper(async2);
var promise3 = promiseHelper(async3);
promise1('test').then(function(result){
console.log('异步1结果:'+result);
return promise2(result);
}).then(function(result){
console.log('异步2结果:'+result);
return promise3(result);
}).then(function(result){
console.log('异步3结果:'+result);
});
结构是不是清晰了很多?从此无论异步函数有少,都只需要写一层回调,看起来也很舒服,then(下一步)-then(下一步)-then(下一步)
但这还不是我们的最终目标,记住异步的最终解决是终结异步,使用同步方式写异步代码!generator的出现,使得这种幻想成为现实。
(前方高能,逻辑较差或不求甚解的同学请跳到最后直接使用结果)
简单认识一下generator函数:
function* test(){ // 定义时多一个*
yield 1; // 支持 yield 关键字(注意普通函数是不支持的)
var r = yield 2;
console.log(r);
return 3;
}
var g = test(); // 这儿获取到的不是返回值3,而是一个generator对象
console.log(g); // Generator {}
var r1 = g.next(); // generator函数分段执行,next()时到达第一个yield处
console.log(r1);// Object { value=1, done=false}
var r2 = g.next();
console.log(r2);// Object { value=2, done=false}
var r = g.next(7); // next方法可以传入参数,函数内的r能获得这个入参
console.log(r);// Object { value=3, done=true}
值得注意的是,next方法可以传入参数,这个很关键,设想一下,yield一个异步函数,这时异步函数执行,generator函数中断,等待异步函数回调执行时,调用next并传入结果,那generator函数往下运行,并且获得了这个异步函数的结果,是不是很棒?
不妨按该思路实现一下:
function* test(){ // 定义时多一个*
var result = yield doAsync('test');
console.log(result);
}
var async = function (param, callback){
setTimeout(function(){
var result = param+'->async';
callback(result)
},2000);
}
var doAsync = function(param){
async(param, function(result){
g.next(result);
});
}
var g = test();
g.next(); // 2秒后控制台打印出test->async
可以看出,我们成功地用类似同步的方式获得了异步函数的结果(除了多一个yield)
当然,看起来这却没有降低复杂度,反而更复杂了!似乎并没有价值,实际上我们也不是这么用的。
让我们再定义一个很关键的generator处理函数:
var generatorHandle = function(generator){
var g = generator();
function ret(){
var r = g.next.apply(g, arguments); // 将入参(即上次ret时注册的回调函数获得的响应)通过next传递到generator内
if(!r.done) { // 当generator没有走完时,注册回调函数获得响应,继续递归调用ret
var value = r.value;
if(value.then) { // 如果value是promise的处理
value.then(function(){
ret.apply(null, arguments);
});
} else { // value不是promise
value(function(){
ret.apply(null, arguments);
})
}
} else {
//console.log('所有异步完成,结果:'+r.value);
}
}
ret();
}
然后使用起来就是这样的:
generatorHandle(function *(){
var r1 = yield promise1('test');
console.log('异步1结果:'+r1);
var r2 = yield promise2(r1);
console.log('异步2结果:'+r2);
var r3 = yield promise3(r2);
console.log('异步3结果:'+r3);
return r3;
});
看到这简单明了的调用,简直了~~~
尝试用generator处理常见异步逻辑。
浏览器端最典型的异步当然是ajax了,我们用generator方式写一写ajax试试呢:
generatorHandle(function *(){
var res1 = yield $.ajax({
url: '/test1.json'
});
console.log(res1);
var res2 = yield $.ajax({
url: '/test2.json',
data: res1
});
console.log(res2);
}
由于jquery的ajax返回的是promise,所以可以像上面那样写。$.get也可以,处理一下就行:
var get = promiseHelper($.get);
generatorHandle(function *(){
var res1 = yield get('/test1.json');
console.log(res1);
var res2 = yield get('/test2.json', {data:res1});
console.log(res2);
})
哇哦~这真是异步代码么~是的,再异步不过了。
服务端最典型的异步读取文件,也是毫无压力:
var fs = require('fs');
var path = require('path');
var readFile = promiseHelper(fs.readFile);
generatorHandle(function *(){
var content = yield readFile(path.resolve(processor.cwd(), '/test.json'));
console.log(content);
});
firefox与chrome都已经支持generator了哦,后端node更是早就有generator特色的框架了,再不用就out了~