之前看過的內容,感受忘得差很少,最近抽空又看了一次,果真書讀百遍其義自見javascript
Generator函數能夠實現函數內外的數據交換和執行權交換。java
從第一次調用next
開始,從函數頭部開始執行,執行到第一個yield
語句時,把執行權交出到函數外部,並返回該yield
語句右值,同時在此處暫停函數異步
在下一次調用next
時候(能夠傳遞參數),把執行權返還給函數內部,同時把參數賦值給上一次暫停的yield
語句的左值,並從該行到開始執行到下一個yield
前,並一直循環該過程函數
須要注意的是,yield
語句的左值,不能由右值賦值,如 let a = yield 3
,a
的值並不等於3,a
的只能由函數外部調用next
時傳入的參數賦值。ui
function test() { return 3; } function* gen(){ console.log(0); let yield1 = yield 1; console.log('yield1 value: ', yield1);// yield1: 2 let yield2 = yield test(); console.log('yield2 value: ', yield2);// yield2: 4 return 3; } let gen1 = gen(); let next1 = gen1.next(); console.log('next1 value: ', next1);// next: { value: 1, done: false } let next2 = gen1.next(2); console.log('next2 value: ', next2);// next: { value: 3, done: false } let next3 = gen1.next(4); console.log('next3 value: ', next3);// next: { value: undefined, done: true }
console.log(0)
yield1 = yield 1
,此時會把表達式右值返回, 即返回 1
next1 = {value: 1, done: false}
, 接着輸出 next1
gen
函數內部在yield1 = yield 1
處暫停 yield1 = yield 1
開始執行2
, 第一次調用已經執行了該yield
語句,因此並不會返回右值,而是會進行賦值操做,把傳入的參數 2
賦給 yield1
console.log('yield1 value: ', yield1)
, 此時yield1 = 2
yield2 = yield test()
, 此時會把表達式右值返回, 即返回 3
next2 = {value: 3, done: false}
, 接着輸出 next2
gen
函數內部在yield2 = yield test()
處暫停 yield2 = yield test()
開始執行4
, 進行賦值操做,此時yield2 = 4
console.log('yield2 value: ', yield2)
, 此時的 yield2
值爲4yield
語句,因此一直執行執行到函數尾部return 5
next3 = {value: 5, done: true}
, 接着輸出 next2
咱們發現Generator函數的執行就是一個循環調用next的過程,天然的想到使用遞歸來實現自動執行
this
function* gen() { let a = yield 1; let b = yield 2; let c = yield 3; } var g = gen(); var res = g.next(); while(!res.done){ console.log(res.value); res = g.next(); }
最簡單的幾行代碼,就實現了Generator的"自動執行",但有一個致命的缺點,代碼裏若是有一步異步操做,而且下一步的操做依賴上一步的結果才能執行,這樣的代碼就會出錯,沒法執行,代碼以下code
function* gen() { let file1 = yield fs.readFile('a', () => {}); let file2 = yield fs.readFile(file1.name, () => {}); } var g = gen(); var res = g.next(); // 異步操做,執行file2的yield時 // file1的值爲undefined while(!res.done){ res = g.next(res.value); }
這就十分尷尬了...使用Generator的一個初衷就是爲了不多層次的回調,寫出同步代碼
,而咱們如今又卡在了回調上,因此須要使用Thunk函數對象
開發中多數狀況都不會單獨使用Thunk函數,可是把Thunk和Generator結合在一塊兒使用時,就會發生奇妙的化學反應,能夠用來實現Generator函數的自動執行。遞歸
Thunk化用一句話總結就是,將一個具備多個參數且有包含一個回調函數的函數轉換成一個只接受回調函數做爲參數的單參數函數,附一段網上的實現ip
const Thunk = function(fn) { return function (...args) { return function (callback) { return fn.call(this, ...args, callback); } }; };
具體原理很少贅述,按照我的理解,函數Thunk化,就是把帶有回調函數的函數拆分爲兩步執行
// 普通函數 function func(a, b, callback){ const sum = a + b; callback(sum); } // 普通調用 func(1, 2, alert); // 對函數進行Thunk化 const ft = thunkify(func); // Thunk化函數調用 ft(1, 2)(alert);
包含異步操做的例子,在執行fs.readFile(fileName)
這第一步操做值以後,數據已經拿到,可是不對數據進行操做,而是在第二步的(err, data) => {}
回調函數中進行數據操做
let fs = require('fs'); // 正常版本的readFile fs.readFile(fileName, (err, data) => {}); // Thunk版本的readFile fs.readFile(fileName)((err, data) => {});
目前結合Thunk
和Promise
均可以實現
上面報錯的例子,把readFile
Thunk化以後,問題就可以獲得解決,
let thunkify = require('thunkify'); let readFileThunk = thunkify(fs.readFile); function* gen() { let file1 = yield readFileThunk('a'); let file2 = yield readFileThunk(file1.name); } var g = gen(); var r1 = g.next(); r1.value(function (err, data) { // 這個回調就是readFileThunk('a')的回調 var r2 = g.next(data); // 等價於file1 = data; r2.value(function (err, data) { if (err) throw err; g.next(data); }); });
執行next
後返回對象中的value
,再也不是一個簡單的值,而是一個回調函數,即readFileThunk
的第二步操做,在這個回調函數裏,能夠取得異步操做的結果,更重要的是能夠在這個回調函數中繼續調用next
,把函數的執行權返還給gen函數內部,同時把file1的值經過next
的參數傳遞進去,整個遞歸就能一直運行。
沿用上面的例子,把readFile
包裝成一個Promise對象
const readFile = function (fileName){ return new Promise(function (resolve, reject){ fs.readFile(fileName, function(error, data){ if (error) return reject(error); resolve(data); }); }); }; function* gen() { let file1 = yield readFileThunk('a'); let file2 = yield readFileThunk(file1.name); } var g = gen(); var r1 = g.next(); r1.value.then(function (data) { // 這個回調就是resolve(data) var r2 = g.next(data); // 等價於file1 = data; r2.value.then(function ( data) { if (err) throw err; g.next(data); }); });
經過在then
裏執行回調函數,獲取到上一步操做的結果和交回執行權,並把值傳遞迴gen函數內部,實現了遞歸執行
進一步封裝,能夠獲得如下的代碼
let Bluebird = require('bluebird'); let readFileThunk = Bluebird(fs.readFile); function run(fn) { const gen = fn(); function next(err, data) { const result = gen.next(data); if (result.done) { result.value; } else { result.value.then((data) => { next(data); }); } } // 遞歸執行 next(); } run(function* g() { let file1 = yield readFileThunk('a'); let file2 = yield readFileThunk(file1.name); });