co模块是koa框架实现的关键技术,主要解决的是node.js的回调函数嵌套过多的问题。
它用到了ES6的新特性generator函数, promise技术,以及thunk函数。
回调地狱问题:异步函数因为其结束时间的不确定性,只能在其回调函数中处理其产生的数据。
因此多个异步函数结果需要顺序执行时候,就只能通过回调函数一步步的嵌套执行,造成代码可读性很差。
我的几点理解:
1.generator函数可以将函数分段执行,执行每段后交出控制权,只有下次next才会继续执行。然而实际上它的yield与next方法都是同步的,连续next的话,被yield的异步函数并不会同步执行。
用generator函数解决回调地狱的方法是,把next写在上一个异步操作的回调函数里,从而使操作同步。
2.promise技术是一个包装了一些方法的语法糖。因为每次then中resovle函数被调用的时机,就是普通写法中回调函数被调用的时机,没什么本质区别。
它的好处是,把回调函数的嵌套换成了连续then的表示形式,没有了嵌套可读性好了很多,异常处理更简单清晰。
3.thunk函数语法糖都不算,只是将函数转化了下格式。 将A形式的function(paras) 转化成B形式的function(callback) paras inside,
这样调用时候,用户使用A形式,直接传参数给方法;而co模块使用B形式,传入自己的callback来控制函数执行的顺序,进行流程管理。
它的好处是,把函数和参数包装在一起给co当作函数参数使用,而不是立刻执行。
简易版co函数的实现原理
//co核心实现
function co(generator) {
return
function(finishCallback) {
//co的callback函数
var gen = generator();
//获得generator指针对象
nextStep();
//通过异步函数的callback实现nextStep的递归调用
function nextStep(err, result) {
if
(err)
return
finishCallback(err);
var step = gen.next(result);
//执行一步next
if
(!step.done) step.value(nextStep);
//没yield完,执行该异步函数,并且将nexStep作为其callback递归调用
else
finishCallback(
null
, step.value);
//yield完了,终止
}
}
}
//模拟的异步操作
function async(data) {
return
function(callback){
setTimeout(function () {
console.log(
'async done: '
+data);
callback();
},
2000
);
}
}
//流程控制函数
var gen = function *generator(){
console.log(
"start"
);
var a=yield async(
"async 1"
);
var b=yield async(
"async 2"
);
return
"finish"
;
}
//同步执行流程
var coo=co(gen);
coo(function(err,data){
if
(err !=
null
)console.log(err);
else
console.log(data);
});
|
输出:
start
async done: async 1
async done: async 2
finish
可以观察到两个异步操作分别等待了2s,说明他们之间是同步执行的。
代码比较清楚,就是每一步yield出来一个异步方法,然后将nextStep方法作为回调函数传给该异步方法 ; 递归执行nextStep方法,直到返回的结果done属性为真。
实际的co模块采用promise实现,原理和简易版没什么区别。
使用co模块方法
co=require(
'co'
);
//模拟的异步操作
function async(data) {
return
function(callback){
setTimeout(function () {
console.log(
'async done: '
+data);
callback();
},
2000
);
}
}
//流程控制函数
var gen = function *generator(){
console.log(
"start"
);
var a=yield async(
"async 1"
);
var b=yield async(
"async 2"
);
return
"finish"
;
}
//同步执行流程
co(gen).then(function(data){
console.log(data);
},function(err){
console.log(err);
});
|
输出:
start
async done: async 1
async done: async 2
finish
输出与上面一样,同样可以观察到时间等待,说明同步成功了。