轉: ES6異步編程: co函數庫的含義與用法

轉: ES6異步編程: co函數庫的含義與用法

co 函數庫是著名程序員 TJ Holowaychuk 於2013年6月發佈的一個小工具,用於 Generator 函數的自動執行。程序員

好比,有一個 Generator 函數,用於依次讀取兩個文件。es6

var gen = function* (){
    var f1 = yield readFile('./foo.txt');
    var f2 = yield readFile('./bar.txt');
    console.log(f1.toString());
    console.log(f2.toString());
};

co 函數庫可讓你不用編寫 Generator 函數的執行器。編程

var co = require('co');
co(gen);

上面代碼中,Generator 函數只要傳入 co 函數,就會自動執行
co 函數返回一個 Promise 對象,所以能夠用 then 方法添加回調函數。數組

co(gen).then(function(){
    console.log('generator函數執行完畢了');
});

co函數庫的原理

爲何 co 能夠自動執行 Generator 函數?
前面文章說過,Generator 函數就是一個異步操做的容器。它的自動執行須要一種機制,當異步操做有告終果,可以自動交回執行權。
兩種方法能夠作到這一點。promise

  • 回調函數。將異步操做包裝成 Thunk 函數,在回調函數裏面交回執行權。
  • Promise 對象。將異步操做包裝成 Promise 對象,用 then 方法交回執行權。

co 函數庫其實就是將兩種自動執行器(Thunk 函數和 Promise 對象),包裝成一個庫。使用 co 的前提條件是,Generator 函數的 yield 命令後面,只能是 Thunk 函數或 Promise 對象。併發

上一篇文章已經介紹了基於 Thunk 函數的自動執行器。下面來看,基於 Promise 對象的自動執行器。這是理解 co 函數庫必須的。異步

基於Promise對象的自動執行

仍是沿用上面的例子。首先,把 fs 模塊的 readFile 方法包裝成一個 Promise 對象。異步編程

/**
 * 基於Promise的自動執行器
 */

var fs = require('fs');

var readFile = function(fileName) {
    return new Promise(function(resolve, reject) {
        fs.readFile(fileName, function(err, data) {
            if(err) reject(err);
            resolve(data);
        });
    });
};

var gen = function* (){
    var f1 = yield readFile('./module-i.es');
    var f2 = yield readFile('./module-i.js');
    console.log(f1.toString());
    console.log(f2.toString());
};

而後,手動執行上面的 Generator 函數。函數

var g = gen();
g.next().value.then(function(data) {
    g.next(data).value.then(function(data){
        g.next(data);
    });
});

手動執行其實就是用 then 方法,層層添加回調函數。理解了這一點,就能夠寫出一個自動執行器。工具

function run (gen) {
    var g = gen();

    function next (data) {
        var result = g.next(data);
        if(result.done) return result.value;
        result.value.then(next);
    }
    next();
}

run(gen);

上面代碼中,只要 Generator 函數還沒執行到最後一步,next 函數就調用自身,以此實現自動執行。

co函數庫的源碼

co 就是上面那個自動執行器的擴展,它的源碼只有幾十行,很是簡單。
首先,co 函數接受 Generator 函數做爲參數,返回一個 Promise 對象。

function co(gen) {
    var ctx = this;
    

    return new Promise(function(resolve, reject){

        //在返回的 Promise 對象裏面,co 先檢查參數 gen 是否爲 Generator 函數。若是是,就執行該函數,獲得一個內部指針對象;若是不是就返回,並將 Promise 對象的狀態改成 resolved 。
        
        if(typeof gen === 'function') { gen = gen.call(ctx); }
        if(!gen || typeof gen.next !== 'function') return resolve(gen);
    });

    //接着,co 將 Generator 函數的內部指針對象的 next 方法,包裝成 onFulefilled 函數。這主要是爲了可以捕捉拋出的錯誤。
    onFulfilled();
    function onFulfilled (res) {
        var ret;
        try {
            ret = gen.next(res);
        }catch(e){
            return reject(e);
        }
        next(ret); //反覆調用自身
    }
    
    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) + '"'));
        }
    });
}

上面代碼中,next 函數的內部代碼,一共只有四行命令。

  • 第一行,檢查當前是否爲 Generator 函數的最後一步,若是是就返回。
  • 第二行,確保每一步的返回值,是 Promise 對象。
  • 第三行,使用 then 方法,爲返回值加上回調函數,而後經過 onFulfilled 函數再次調用 next 函數。
  • 第四行,在參數不符合要求的狀況下(參數非 Thunk 函數和 Promise 對象),將 Promise 對象的狀態改成 rejected,從而終止執行。

併發的異步操做

co 支持併發的異步操做,即容許某些操做同時進行,等到它們所有完成,才進行下一步。
這時,要把併發的操做都放在數組或對象裏面。

// 數組
co(function* (){
    var res = yield [
        Promise.resolve(1);
        Promise.resolve(2);
    ];
    console.log(res);
}).catch(onerror);

// 對象
co(function* (){
    var res = yield {
        '1': Promise.reolve(1);
        '2': Promise.resovle(2);
    };
    console.log(res);
}).catch(onerror);
相關文章
相關標籤/搜索