Generator函數是ES6提供的一種異步編程解決方案
npm
先來看個Generator的簡單的用法編程
function* read() {
console.log(100);
let a = yield '200';
console.log(a);
let b = yield 300;
console.log(b);
return b;
}
let it = read();
console.log(it.next('400'));
console.log(it.next('500'));
console.log(it.next('600'));
console.log(it.next('700'));
複製代碼
打印結果爲promise
100
{ value: '200', done: false }
500
{ value: 300, done: false }
600
{ value: '600', done: true }
{ value: undefined, done: true }
複製代碼
首先,Generatror函數有兩個特徵:bash
yield語句在英語裏的意思就是「產出」,yield會將函數分割成好多個部分,每產出一次,就暫停一次
異步
上例中每一次迭代器調用next的所執行的語句能夠理解爲下圖異步編程
it.next('400')
,先執行了紅色區域內代碼,這裏沒有接收參數400的語句,是無效的,程序先打印出100,再執行yield,next返回的{value:xxx,done:bool}對象中的value值,即yield後面跟着的值,此時迭代沒有執行完,done爲false,因此接着打印出{ value: '200', done: false }it.next('500')
,執行紅色和藍色區間內的代碼,yield 前面的等號前的變量,就是接收此次next傳進來的參數的,也就是說a等於500,yield的輸出同裏,會輸出{ value: 300, done: false }it.next('600')
,到了藍色和黑色區間內的代碼,b接收next參數600,最後return b,會將b的值做爲value,同時迭代器執行完畢,done爲true,因此返回結果爲 { value: '600', done: true }it.next('700')
,已經沒有接收next參數的地方,也沒有return,即value爲undefined,done仍然是完成,返回{ value: undefined, done: true }Generator函數有多種理解角度。從語法上,能夠把它理解成一個狀態機,封裝了多個內部狀態。
函數
執行Generator函數會返回一個遍歷器對象,也就是說,Generator函數除了狀態機,仍是一個遍歷器對象生成函數。
post
先來看一個讀取文件的例子
ui
讀取文件1.txt中的內容content1,content1又是一個文件的路徑,繼續讀取文件content1中的內容content2,並返回結果spa
function read(path) {
return new Promise(function (resolve, reject) {
require('fs').readFile(path, 'utf8', function (err, data) {
if (err) reject(err);
resolve(data);
})
})
}
function* r() {
let content1 = yield read('1.txt', 'utf8');
let content2 = yield read(content1, 'utf8');
return content2;
}
let it = r();
it.next().value.then(function(data1){
it.next(data1).value.then(function(data2){
console.log(data2);
console.log(it.next(data2).value);
})
})
複製代碼
先將異步readFile方法promise化,即調用read方法會獲得一個執行readFile方法的promise;
Generator生成器函數依次輸出(yield)各文件的內容,最後return返回
使用時,先獲得迭代器對象it,第一次調用next()獲得的value即爲讀取1.txt的promise,既然是promise,調用其then方法處理成功回調,data1就是1.txt讀取成功時返回的內容,將data1做爲下一個next的參數,即content1這時就是data1,同理next返回的value是讀取content1文件的promise,因此data2就是成功讀取content1的返回值
上例能夠簡化read函數,利用一些庫提供的promisify能夠直接將函數promise化,如
let bluebird = require('bluebird');
let fs = require('fs');
let read = bluebird.promisify(fs.readFile);
複製代碼
promisify參照 juejin.im/post/5ab4f4…
promise的使用方法參照juejin.im/post/5aae65…
可是上面不停的調用next()方法,並容易造成嵌套,也是咱們但願簡化的
這裏使用co庫,來幫咱們自動的將generator迭代
安裝: npm install co 上例能夠簡化爲
let bluebird = require('bluebird');
let fs = require('fs');
let co = require('co');
let read = bluebird.promisify(fs.readFile);
function* r() {
let content1 = yield read('1.txt', 'utf8');
let content2 = yield read(content1, 'utf8');
return content2;
}
co(r()).then(function (data) {
console.log(data)
})
複製代碼
那麼co庫是怎麼實現自動迭代的呢,基於上例這裏簡單實現一下
function co(it) {
return new Promise(function (resolve, reject) {
function step(d) {
let { value, done } = it.next(d);
if (!done) {
value.then(function (data) { // 2,txt
step(data)
}, reject)
} else {
resolve(value);
}
}
step();
});
}
複製代碼
首先從用法能夠看出,co執行會返回一個promise,用then註冊成功/失敗回調,因此先return 一個promise co將迭代器it做爲參數,這裏每調用一次step,就執行一次next 既然是自動執行,那麼promise的executor中先執行一次it.next()方法,返回value和done;value是一個pending的Promise;若是done=false,說明尚未走完,繼續在value.then的成功回調中執行下一次next,即調用step方法;直到done爲true,走完全部代碼,調用resolve;中間有任何一次next異常,直接調用reject,中止迭代