ES6 Generator

Generator函數時ES6提供的一種異步編程的解決方案。
在語法上,我們可以把Generator函數理解爲一個狀態機,封裝了很多內部狀態。執行Generator函數會返回一個遍歷器對象。
Generator函數區別於普通函數的特徵在:
1.function命令與函數名之間有一個星號
2.函數體內部使用yield語句定義不同狀態
例如:

function* helloWorld(){
 yield 'hello';
 yield 'world';
 return 'goodbye';
}
 var p = helloWorld();

調用Generator函數會並不會立即執行,返回的也不是函數運行的結果,而是一個返回一個指向內部狀態的指針對象,即遍歷器對象。
必須調用遍歷器的next方法,使指針移向下一個狀態。即每次調用next方法,內部指針從函數頭部或上一次停下來的地方開始執行,直到遇到下一條yield語句(或return語句爲止)。Generator函數是分段執行的,yield語句是暫停執行的標記,而next方法可以恢復執行。

console.log(p.next());//{value:'hello',done:false}
console.log(p.next());//{value:'world',done:false}
console.log(p.next());//{value:'goodbye',done:true}
調用生成器的過程做了什麼?

第一次調用Generator函數開始執行,直到遇到第一條yield語句爲止。next方法返回一個對象,它的value屬性就是當前yield語句的值hello,done屬性的值false表示遍歷還沒有結束。
第二次調用,Generator函數從上次yield語句停下的地方,一直執行到下一條yield語句,next方法返回獨享的value屬性就是當前yield語句的值world,done屬性的值false表示遍歷還沒結束。
第三次調用,Generator函數從上次yield語句停下地方一直執行到return 語句,next方法返回的對象的value屬性就是緊跟在return語句後面的表達式的值,done屬性爲true表示遍歷已經結束。

當我們調用一個生成器的它並不是立即執行,而是返回一個已經暫停的生成器對象。我們可以將這個生成器對象視爲一次函數調用,只不過立即凍結了,它恰好在生成器函數的最頂端的第一行代碼之前被凍結了。
每當我們調用.next()方法時,函數調用自身解凍並執行到下一個yiled表達式,再次暫停。最終抵達生成器的末尾,所以返回的結果中的done值爲true,next方法返回的對象的value屬性就是緊跟在return語句後面的表達式的值(如果沒有return語句,則value屬性的值爲undefined)。

每當生成器執行yields語句,生成器的堆棧結構(本地變量,參數,臨時值,生成器內部當前的執行位置)被移除堆棧。然而生成器對象保留了對這個堆棧結構的引用(備份),所以稍後調用.next()可以重新激活堆棧結構並繼續執行。

值得一提的點

生成器不是線程,在支持線程的語言中,多段代碼可以同時運行,通常導致靜態條件和非確定性,不過同時也能帶來不錯的性能。生成器則完全不同。當生成器運行的時,它和調用者處於同一線程中,擁有確定的執行順序,永不併發。與系統線程不同的是,生成器只有在其函數體內標記爲yield的點纔會暫停。

Generator函數的用法

Generator執行結束後會將控制權交還給調用者
Generator實際上是一種特殊的迭代器,不過Nodejs主流的場景是將異步回調變成同步模式。
用TJ大神開發的co模塊來解釋Generator的用途:
1.基於co函數實現自定義延遲時間

var require('co');
co(function*(){
  var now = Date.now();
  yield sleep(500);
  console.log(Date.now()-now);
})();

function sleep(ms){
  return function(cb){
    setTimeout(cb,ms);
  }
}

基於co,我們就可以寫出類似這樣的業務代碼:

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

在這之前,我們只能用洋蔥式回調寫法:


db.query('select url from xxx', function(rs){
    rs.forEach(r){
        getUrl(r.url, function(content){
            ...
        });
    }
});
co實現原理

看一個簡化版的co代碼,算是co的一個框架

co(function* (input){
  var now = Date.now();
  yield sleep200;
  cobsole.log(Date.now() - now);
});

function co(fn){
  var gen = fn();
  next();
  function next(res){
    var ret;
    ret = gen.next(res);
    if(ret.done){
      return;
    }
    //執行回調
    if(typeof ret.value == 'function'){
       ret.value(function(){
         next.apply(this.arguments);
      });
      return;
    }
    throw 'yield target no supported';
  }
}
function sleep200(cb){
  setTimeout(cb,200);
}

代碼的核心在Generator的流程控制以及回調函數的執行

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章