首先我们来看这样一个需求:
有这样一组操作:pre,a1,a2,a3,b1,b2,b3,suf,每个操作都花费不确定的时长,这可能需要访问网络或者等待事件响应,总之我需要传入一个回调函数然后随它开心什么时候去调用。
假定我们的需求是:必须在pre回调完成后才能执行A和B,并且A和B各自需要保证执行顺序,而A和B之间则无需考虑顺序。在A和B都执行完之后,必须执行suf。用ES5的回调函数去实现它无疑是一件令人崩溃的事情。
现在来看看我是怎么做的:
new syn(function*(method){
console.log('syn in');
let pre = yield method.exc(callback, "pre", this);
console.log(pre);
method.syn(function*(m){
yield m.exc(callback, "a1", this);
yield m.exc(callback, "a2", this);
yield m.exc(callback, "a3", this);
return "你开心就好";
});
method.syn(function*(m){
let [w1] = yield m.exc(callback, "b1", this);
let w2 = yield m.exc(callback, "b2", this);
let w3 = (yield m.exc(callback, "b3", this))[0];
return w1+w2[0]+w3;
});
method.syn(function*(m){
m.exc(callback, "c1", this);
m.exc(callback, "c2", this);
m.exc(callback, "c3", this);
return yield m.muti();
});
let [A, B, C] = yield method.muti();
console.log(A);
console.log(B);
console.log(C);
method.exc(callback, "suf1", this);
method.exc(callback, "suf2", this);
let [suf1, suf2] = yield method.muti();
console.log(suf1);
console.log(suf2);
console.log("syn out");
});
function callback(name, func) {
console.log("执行函数:"+name);
setTimeout(func, Math.ceil(Math.random()*2)*1000, "函数回调:"+name);
}
在上面的例子中我使用了A,B,C三组并行处理的数据,以便更清晰的表现函数执行过程:
在上述例子中,pre执行完毕时,对a1,b1,c1连续调用,其中A、B要求上一个调用有结果才发起下一个调用,C为同时调用,等待全部结果一起返回。
在syn块中,仅当上一个yield表达式取得结果后,下面的代码才能够运行
实际上syn的实现原理十分简单,只是利用了ES6的语法糖,其本质上依然是回调函数的嵌套,并没有开启新的线程。但采用syn块后,代码的可读性惨遭巨大提升。
以下是syn方法的具体代码:
function syn(func, onreturn=null) {
let method = {};
let it = func.call(func, method);
let ly;
let results;
let count = 0;
let callback = (index,result) =>{
count--;
if(ly.value) {
results[index] = result;
if(!count) {
ly = it.next(results);
}
} else {
ly = it.next(result);
}
if(onreturn&&ly.done) {
//console.log(ly.done);
onreturn(ly.value);
}
};
// 返回之前请求的全部值
method.muti = ()=>{results = new Array(count);return true;};
// 特别的,如果对多路并发请求需要进行并行处理的,可以调用syn方法‘开启子线程’
// syn方法应当配合muti使用
method.syn = (fun)=>{
let index = count++;
new syn(fun, (v)=>{
callback(index, v);
});
return false;
};
// 执行外部方法,此处必须使用function,不能使用箭头函数,否则arguments无法获取外部传入的参数
method.exc = function(){
let index = count++;
let target = arguments[0];
let args = [];
for(let i=1; i<arguments.length; i++) {
if(func==arguments[i]) {
args.push(function(){
callback(index, arguments);
});
} else {
args.push(arguments[i]);
}
}
target.apply(func, args);
return false;
};
// 扩展
method.next = (v)=>it.next(v);
ly = it.next();
};