首先我們來看這樣一個需求:
有這樣一組操作: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();
};