Generator用法詳解+co

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

  • function關鍵字與函數名之間有一個星號
  • 函數體內部使用yield語句,定義不一樣的內部狀態

yield語句在英語裏的意思就是「產出」,yield會將函數分割成好多個部分,每產出一次,就暫停一次
異步

  • Genenrator是一個生成器,調用Genenrator函數,不會當即執行函數體,只是建立了一個迭代器對象,如上例中的it就是調用read這個Generator函數獲得的一個迭代器
  • 迭代器有一個next方法,調用一次就會繼續向下執行,直到遇到下一個yield或return
  • next()方法能夠帶一個參數,該參數會被當作上一條yield語句的返回值,並賦值給yield前面等號前的變量
  • 每遇到一個yield,就會返回一個{value:xxx,done:bool}的對象,而後暫停,返回的value就是跟在yield後面的返回值,done表示這個generator是否已經執行結束了;
  • 當遇到return 時,return後的值就是value指,done此時就是true;
  • 函數末尾若是沒有return,就是隱含的return undefined;

上例中每一次迭代器調用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

generator用來與promise搭配使用 將異步回調看起來變成同步模式

先來看一個讀取文件的例子
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迭代

co庫

安裝: 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,中止迭代

相關文章
相關標籤/搜索