1. 程式人生 > >ES6新特性:Generator以及衍生NPM庫CO入門

ES6新特性:Generator以及衍生NPM庫CO入門

2015年ES6的釋出帶來了一系列的JS新特性,Generator在其中屬於一個比較重要的新特性,這裡就詳細地介紹一下。

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

以這個最基本的Generator為例,他和普通函式有以下兩點區別:一個是使用了function*,有點類似於我們在強型別語言中常見的指標;另一個是在函式內部有Yield關鍵字。
yield的作用在於暫停函式,並且返回yield右邊的值。

yield 1+1 //返回2
yield 1 == 1 //返回true

我們知道了yield的作用,那麼它在Generator中是有通過什麼方式使用的呢?

Generator在建立時不會執行函式(再一次類比於指標,就像是我們只建立了一個指向該函式的指標但是沒有執行),只有我們使用了*.next()之後才會開始執行,並且在下一個yield處停止,參考以下例子

function* test(){
    console.log('hello'); 
    yield"i'm stopped");
    console.log('world');
}

var g = test();
var
yield_value = g.next();//{value:'i'm stopped', done:false} g.next();
  • 我們先是建立了一個test的例項,這時什麼也沒有發生
  • 然後我們呼叫了next()語句,輸出hello,之後在yield暫停,並且返回i’m stopped;給yield_value
  • 接下來再次執行next()語句,輸出world,程式結束(done引數表示函式是否已經執行完畢,這時的done為true)。

我們需要注意的是,yield返回了右邊表示式的值,但是在函式內部將yield表示式直接賦值是不可行的,比如以下這個例子

function* foo
(x) {
var y = 2 * (yield (x + 1)); var z = yield (y / 3); return (x + y + z); } var a = foo(5); a.next() // Object{value:6, done:false} a.next() // Object{value:NaN, done:false} a.next() // Object{value:NaN, done:true}
  • 我們想要的結果應該和沒有yield一樣的,可是就像上面顯示的一樣,next函式yield只會將右邊表示式的值傳遞給next,但是不能夠傳遞給左邊的表示式。
  • 準確來說,yield會將next()裡面的引數傳遞給左邊的表示式。如果我們寫成如下形式的話就可以讓函式輸出我們需要的結果了。
function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
var i = a.next();
i = a.next(i.value);
i = a.next(i.value);
console.log(i.value);

進階玩法,在不清楚有多少個yield的情況下可以以這種方式解決

function* foo(x) {
  var y = 2 * (yield (x + 1));
  var z = yield (y / 3);
  return (x + y + z);
}

var a = foo(5);
var i = {value:0};
while (!(i = a.next(i.value)).done);
//不斷儲存之前yield的值再傳遞給下一個next直到函式結束(done為true)
console.log(i.value);//21
  • 注意,yield只能在generator,即有*號的函式中使用,普通函式中使用會出錯
  • 在表示式中使用需要加上括號,不然會認為這是一個變數。

其他的應用方式可以參考ES6入門比較詳細地介紹了各種屬性和用法;但是我這裡只是為了入門,一些高階技巧沒有實際的使用場景的話很難記住,這裡就不做介紹了。

Generator的功能

介紹完Generator,他到底有什麼用呢?它作為ES6中協程的一個概念引進的。有了yield的幫助,我們可以自行定義語句執行順序,協調好各個程式碼邏輯。我們這裡使用它來解決同步程式設計中callback hell的問題,但是我們也要明白,generator不僅僅是解決callback而已,和promise相比,generator能夠操控語句執行,順帶地解決了非同步,而promise是專為同步而生的,他本質只是把callback hell轉換成便於閱讀的形式,而不是從根本改變程式碼的執行形式。
CO庫使用Generator的特性來生成同步的函式。具體看下面的例子

co(function *(){
    var rs = yield db.query('select url from xxx');
    rs.forEach(rs){
        var content = yield getUrl(rs.url);
        ...
    }
})();

只要用CO將generator套起來,裡面的程式碼不需要其他任何的改變,他就會使得程式碼等待yield右邊表示式執行結束再繼續,實現了同步程式設計又不需要改變程式碼結構。
有一些我們required的模組不支援同步,這時我們可以使用thunkify來轉化一下使用

var thunkify = require('thunkify');
var fs = require('fs');
var read = thunkify(fs.readFile);
read('package.json', 'utf8')(function(err, str){

});

此外,對於GENERATOR我們還有一些擴充套件API的模組Yield可供我們使用
Yield