1. 程式人生 > >generator自執行問題和co原始碼解析

generator自執行問題和co原始碼解析

generator自動執行問題

這裡我覺得對於js引擎,其實仍然是單執行緒的。能達到這麼一種效果,應該是generator函式內部的資料結構的原因。迭代器內部自己能快取其各種狀態。就是一種迭代器。(經我試驗,呼叫方式同步,在其中完成非同步操作時候,如果gen中掉了非同步,非同步的執行還是會在同步之後)
這是一種迭代器,但是在主流node裡,主要用它將非同步變為同步作用。 —– 這裡我認為是利用這種機制實現,本身這種資料結構並沒有這樣的。

柯里化的概念很簡單:只傳遞給函式一部分引數來呼叫它,讓它返回一個函式去處理剩下的引數。
在電腦科學中,柯里化(英語:Currying),又譯為卡瑞化或加里化,是把接受多個引數的函式變換成接受一個單一引數(最初函式的第一個引數)的函式,並且返回接受餘下的引數而且返回結果的新函式的技術。

柯里化的用處是感覺簡化了函式程式設計。讓函式程式設計過程更簡單。 —- 函數語言程式設計還可以再說
當我們走到generator時候,其實就是用yield執行函式並且出讓程式碼的執行權、當我們使用next函式的時候,就可以yield再還回來程式碼的執行權。也就是說讓next自動執行。而自動執行的思路無非是遞迴和迭代。在遞迴和迭代中呼叫next函式。
有大佬寫了個庫co,在co庫中使用兩種方式(一種是用thunk傳入回撥函式,一種是用promise的then來傳入回撥函式來交還執行權)
方式是非常精妙的!!!!
co(function),代表這個函式要用co這個方式進行非同步變同步。就是不停的走迭代器(通過next),如果迭代器返回的當前狀態是沒有結束仍在迴圈中,並且迭代器返回的是一個函式(比如說我們非同步時候,其實就是放一個函式給yield狀態的)。那麼這個時候我們就取迭代器的value值執行它,並將回撥函式傳入–co最經典的地方就是通過傳遞迴調函式,讓結束非同步操作後代碼又回到這個迭代器裡!!!!
回撥函式裡,因為閉包,這個非同步函式可以使用當前變數。回撥函式裡再次呼叫了這個next函式,起到了往下走的同步思路。因為是在回撥函式裡寫的,所有非同步妥妥的。
— 這裡就用到了thunk想法,將函式傳入,代需要執行的時候再執行引數。
程式碼如下:(這個思想的一個簡單例子,不是大神的原始碼,但是基本就是這個實現思路)

co(function *( input ) {
  var now = Date.now();
  yield sleep200;
  console.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) }

那麼按照上述思路,其實我們能做到在非同步完成後在回撥裡執行value.next,讓genertor迭代函式重新獲得執行權繼續執行就可以了。要打成這個目的有兩種方式:
用thunk偏函式的方式,如上述程式碼,將回調函式作為引數傳入給非同步執行的函式;
用promise思想,resolve裡會執行next;(4.0後版本就是這樣的)
現在的版本里核心函式同樣是回撥,但是由於使用了promise還有個核心是promise封裝

/**
 * 這個函式裡執行每次做promise和往後移 ---- 第二次後就是回撥函式
**/
function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
} 
/**
 * 這個函式裡起到封裝promise,執行這個非同步函式,並且還是在回撥中再交回執行權,
 * 這裡就是用promise.then(resolve,rejecct) 這樣的
**/
function next(ret) {
  if (ret.done) return resolve(ret.value);
  var value = toPromise.call(ctx, ret.value);
  if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
  return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
    + 'but the following object was passed: "' + String(ret.value) + '"'));
}

4.0版本里面主要是圍繞這個思路的,剩下的無非就是為了使用者體驗,把各個value給的東西封裝成promise函式,這樣就能滿足上面的回到條件和處理異常(處理異常也是很重要的一點,會丟擲異常),具體而言有封裝這幾個型別到promise:

  • 不是Object的基礎資料型別無非同步需求,可以直接返回做同步操作
  • generatorFunction和Generator,給co裡帶上系統環境變數為當前的value
  • function ,用thunk那種回撥函式的方式,將其封裝成promise
  • array,array封裝成promise
  • object,封裝成promise

最正常的普通函式的封裝方式如下:

function thunkToPromise(fn) {
  var ctx = this;
  return new Promise(function (resolve, reject) {
    fn.call(ctx, function (err, res) {
      if (err) return reject(err);
      if (arguments.length > 2) res = slice.call(arguments, 1);
      resolve(res);
    });
  });
}