前言
在《ES6 非同步程式設計之一:Generator》中實現了一個非同步函式呼叫鏈,它是一個順序呼叫鏈,很類似責任鏈模式,但現實往往不是平鋪直敘的,更多的其實是峰迴路轉,本文將繼續討論更多Generator
的用法。
作為函式的Generator
在之前的示例中,我們更多的是把生成器作為一個迭代器來迴圈呼叫每個函式,但忽視了生成器也是個函式,意味著生成器內部可以實現複雜的業務邏輯,以下的程式碼通過yield
等待檔案讀取結果並對結果進行特定的處理,
function* generator() {
let r1 = yield get(`a`);
if (r1) {
let r2 = yield get(`b`);
if (r2) {
console.log(yield get(`d`));
}
} else {
console.log(yield get(`c`));
}
}
let g = generator();
g.next();
如果get
是個非同步呼叫,以上的程式碼想要能夠執行則需要get
函式執行得到結果後呼叫生成器的next
方法來推進,這要求get
函式能持有生成器物件,這顯然並不容易。
偏函式
偏函式(Partial Function)是對函式定義域的子集定義的函式,形式上就是指定任意部分引數生成一個新的函式,如下:
function sum(a, b, c) {
return a + b + c;
}
function sum1(a) {
return function(b, c) {
return a + b + c;
};
}
function sum2(a, b) {
return function(c) {
return a + b + c;
};
}
sum(1, 2, 3) == sum1(1)(2, 3); //true
sum(1, 2, 3) == sum2(1, 2)(3); //true
一般在設計非同步呼叫api時,我們總是宣告一個引數來接收回撥函式,當和偏函式相結合就變成這樣:
function get(f, callback) {
delay(100, function(s) {
callback(s + `:get ` + f);
});
}
get(`a`, func); //呼叫get時必須立即傳入一個函式
//轉換成偏函式形式:
function partialGet(f) {
return function(callback) {
delay(100, function(s) {
callback(s + `:get ` + f);
});
};
}
let pGet = partialGet(`a`); //可以先生成一個函式
pGet(func); //需要時再傳入回撥函式執行
從上面的例子中可以發現,偏函式能使定義和執行分離,說來巧了,生成器可用於定義業務邏輯而生成器的next
用於推進業務執行,二者也是相互分離的。
生成器和偏函式
基於前面這麼多鋪墊,假設get
就是一個偏函式,如下:
function get(f) {
return function(callback) {
delay(100, function(s) {
callback(s + `:get ` + f);
});
};
}
這意味著,yield get(`a`)
使得next
函式執行的結果其value
屬性值是個函式,該函式的引數是一個能接收get
非同步結果的回撥函式,即:
g.next().value(function(value) {
g.next(value); //value成為yield的返回並繼續推進業務邏輯
});
通過遞迴可以不斷的執行生成器的next
方法,一個全新的通過生成器來實現業務邏輯的run
方法便呼之欲出了,
function run(gen) {
let g = gen();
function next(lastValue) {
let result = g.next(lastValue); //將上一個非同步執行結果傳出給當前的yield並執行下一個yield
if (result.done) {
return result.value;
}
//value是偏函式返回的新函式,它的引數是個用來接收非同步結果的回撥函式
result.value(next); //next作為接收非同步結果的回撥函式
}
next();
}
run(generator);
綜合以上例子不難發現另外一個好處,通過偏函式可以使非同步呼叫api不受生成器的侵入,《ES6 非同步程式設計之一:Generator》中實現的非同步呼叫需要將生成器作為引數,有興趣的話你可以嘗試改造一下之前的示例。
現在通過新編寫的run
函式就可以來執行本文一開始編寫的那個生成器了。
thunkify
偏函式的方案對api和生成器也是有侵入的,他要求:
-
api必須是偏函式形式;
-
生成器定義業務邏輯,每個yield 後面的函式必須是呼叫偏函式;
第二個問題是本方案的核心機制所要求,但第一個問題我們可以通過thunkify
來解決,api依舊按照get(value, callback)
方式定義,但定義生成器時需要將非同步api呼叫通過thunkify
轉換為偏函式,如下:
let Thunkify = require(`thunkify`);
let thunkifiedGet = Thunkify(get);
function get(f, callback) {
delay(100, function(s) {
callback(s + `:get ` + f);
});
}
function* generator() {
let r1 = yield thunkifiedGet(`a`);
if (r1) {
let r2 = yield thunkifiedGet(`b`);
if (r2) {
console.log(yield thunkifiedGet(`d`));
}
} else {
console.log(yield thunkifiedGet(`c`));
}
}