co yield避免嵌套詳細代碼示例。

/**
 * co & yield 培訓例程. TJ's co test, 參考和下載: https://github.com/visionmedia/co
 * 運行環境: 安裝 nodejs v0.11.2以上版本, 推薦IDE: Jetbrains WebStorm 8.0
 * 依賴的包: 請先 npm install -g co thunkify request //全局化安裝,
 * 再到本js所在目錄下 npm link co thunkify request 引用這些全局安裝模塊
 * 執行: node --harmony co.js ,必須帶上--harmony參數才支持 ES6特性
 * WebStorm 調試: 在Run/Debug Configuration -> Node parameters 中添加 --harmony
 * 學習方法: 在WebStorm的Debugger中, 設置斷點, 單步執行. 這樣就能清楚看到yield執行順序
 * Created with WebStorm.
 * User: JimmyCHEN(290958374@qq.com)
 * Created: 2014/1/24 8:17
 * Modified: 2014/2/2 11:20
 * 中文說明編寫: 2014/7/7 13:40
 */
var assert = require('assert');
var co = require('co');
var fs = require('fs');
// co 是利用Generator和yield原理編寫的一個包, 具備運行相似於「協程」的功能。利用co 和 yield 編寫和執行異步操做, 能夠徹底擺脫nodejs的回調深坑,
// 大幅提升代碼溫馨度和團隊生產力. 所以,co是深受回調深坑困然的nodejs碼農們的不二選擇!
// 對於現有的傳統回調式的異步函數(如fs.readFile, http.request等),須要將其封裝(轉換)成爲能夠yield的函數(該函數稱爲「轉換器」), 供yield調用
// 一個轉換器的格式通常是這樣的:
function read(file, encoding) { //這個轉換器只返回一個調用fs.readFile的符合yield要求的函數, 而不是執行fs.readFile
    encoding = encoding || 'utf-8';
    return function(callback) { //轉換器返回的是一個函數, 其參數必須是被異步函數(fs.readFile)回調的callback. 轉換器自己不執行fs.readFile
        fs.readFile(file, encoding, callback);//callback的參數是 (err, result), 這裏的 `result` 最終將被做爲yield read()的返回值.
    }
}
//*上述 function 等價於 var read = thunkify(fs.readFile); //若是採用現成的thunkify模塊的話, 就不用本身寫上面那樣的轉換器函數了, 省事! (留做後文介紹)
//API-1: co(fn)
//異步函數通過上述轉換之後, 就能夠在co裏用yield去執行. 執行yield化的異步函數, 就如執行同步函數同樣簡單溫馨, 再也沒有回調深坑的煩惱. 我和個人小夥伴都驚呆了! 請看大屏幕:
co(function* () { //被 co 的函數必須是個生成器(generator), 用 function* 定義
    var a = yield read('.dbshell'); //異步讀文件1, 內容返回給 a. 在內容沒讀到前, yield將退出函數的執行, 下一句(var b =...)不會被執行.
    // 直到 a 獲得返回內容, 才接着執行下一句. 實際上, 返回值是被fs.readFile中的callback返回, 而後觸發(喚醒)yield 並賦值給a
    //  *請根據實際目錄內容修改文件名稱, 下同
    var b = yield read('config.json');  //異步讀文件2, 內容返回給 b
    var c = yield read('note.txt');     //異步讀文件3, 內容返回給 c
    //三個 read 被順序依次執行, 最後獲得三個值 a,b,c
    console.log([a, b, c]);   // => [5171, 2090, 1477]
})();

//=========================================================================================================//
//co & yield化後的錯誤處理: 用try..catch
co(function* () { //被 co 的函數必須是個生成器(generator), 用 function* 定義
    var a = yield read('.dbshell'); //異步讀文件1, 內容返回給 a
    var b = yield read('config.json');  //異步讀文件2, 內容返回給 b
    var c = yield read('note.txt');  //異步讀文件2, 內容返回給 c
    //上面三個 read 被順序依次執行, 最後獲得三個值 a,b,c
    //錯誤處理
    try {
        var d = yield read('not exists.txt');     //文件不存在, 用於產生錯誤
    } catch (e) {
        console.log(e); // => 輸出: 文件不存在的錯誤
    }

    console.log([a, b, c]);   // => [5171, 2090, 1477]
})();
//=========================================================================================================//
//你也能夠 yield 執行一個 generator 對象, 以支持嵌套:
//用於讀取文件大小的轉換器
function size(file) {
    return function(callback){
        fs.stat(file,function(err, result){
            if(err) return callback(err);
            callback(null, result.size);    //異步執行結束後將返回result.size給yield
        });
    }
}
function* foo(){
    var a = yield size('.dbshell');
    var b = yield size('config.json');
    var c = yield size('note.txt');
    //debugger;
    return [a, b, c]; // => [ 994, 187, 16212 ]
}
function* bar(){
    var a = yield size('test/co.js');
    var b = yield size('test/test.js');
    var c = yield size('test/class.js');
    //debugger;
    return [a, b, c]; // => [ 9023, 2090, 1477 ]
}
co(function*(){
    var results = yield [foo(), bar()];//yield 一個數組對象. //也能夠按下面的例子寫成 [foo, bar]
    //debugger;
    console.log(results);// => [ [ 994, 187, 16212 ], [ 9023, 2090, 1477 ] ]
})();

//=========================================================================================================//
//對於那麼多傳統回調式異步函數, 都去手工編寫一個轉換函數是否是太麻煩了? 別擔憂, 咱們有tunkify. 顧名思義, 它就是現成的轉換器函數:
var thunkify = require('thunkify');
var request = require('request');   //用於http.request的包

//轉換器:thunkify, 將傳統的回調式的函數,轉換成可yield的函數
//轉換request.get函數
var get = thunkify(request.get); //request.get = function (uri, options, callback), where callback = function (err, response, body)

function* results(){
    var a = get('http://www.baidu.com');//這裏只是獲得函數,不會發起請求
    var b = get('http://www.163.com');  //用yield 執行 get纔會返回http.get的callback裏的返回內容
    var c = get('http://www.126.com');
    return yield [a, b, c];//這裏才真正發起http.get請求, 並且是同時發起3次併發請求. 用yield [數組], 將「併發執行」數組中的全部操做
}
co(function*(){//再次強調, co裏的函數必須是生成器!
    var a = yield results; // 每條yield []將發起3次併發執行
    var b = yield results; //兩條 yield 則是順序執行
    console.log(a, b); // => [三個網頁內容] , [三個網頁內容]
    var c = yield [results, results];//這裏將產生6個併發http操做!
    console.log(c);// => 輸出: [[三個網頁內容],[三個網頁內容]]
})();

//=========================================================================================================//
//併發 yield: 逗號表達式中的全部yield將被併發執行.
co(function*(){
    var a = size('test/test.js');
    var b = size('test/class.js');
    var c = size('test/class2.js');
    //debugger;
    return [yield a, yield b, yield c];
})();

//=========================================================================================================//
//下面演示如何取得多個返回值(只要將第2個值附加到第1值上便可) , 例: callback(err, response, body) 中的body值
var get2 = function(uri) {
    return function(callback){
        request.get(uri, function(err, response, body) {
            if(err) return callback(err);
            if(typeof body == 'object') {//如果json,則附加到response上用於yield返回值
                response.bodyObject = body;//*這裏只是爲了演示須要! 實際上若request.get參數裏指定json:true,則response.body 自己就是json了,沒必要另行附加body
            }
            callback(null, response); //返回response
        });
    }
}
function* results2(){
    var a = get2({url:'http://market.huobi.com/staticmarket/ticker_ltc_json.js',json:false});//這裏只獲得函數,不會發起請求
    var b = get2({url:'http://market.huobi.com/staticmarket/ticker_btc_json.js',json:true});//yield get纔會返回callback裏的response
    var c = get2({url:'http://market.huobi.com/staticmarket/depth_ltc_json.js',json:true});//callback(err, response,body)中的body將做爲response.bodyObject返回
    return yield [a, b, c];//這裏才發起請求, 並且是3次併發請求(yield數組對象)
}
co(function*(){
    // 3 concurrent requests at a time
    var a = yield results2; //3個併發get
    var b = yield results2; //3個併發get
    console.log(a, b);      //輸出:[三個網頁內容], [三個網頁內容]
    assert.equal(typeof a[0].body, 'string', 'Error: a[0].body is not a string');//equal:(value, expected, errorMessage)
    assert.equal(typeof a[0].bodyObject, 'undefined', 'Error: a[0].bodyObject is not undefined');
    assert.equal(typeof b[1].bodyObject, 'object', 'Error: b[1].bodyObject is not an Object');
    var c = yield [results2, results2];//6 個併發get!
    console.log(c);//輸出:[[三個網頁內容], [三個網頁內容]]
})();

//=========================================================================================================//
//數組: yield 數組
//yield執行數組, 數組裏的全部步驟都是併發操做. 最後全部返回值組成一個數組.
co(function* yieldArray(){  //!再次強調: co裏必須是個generator, 用 function* 定義
    var a = size('test/co.js');
    var b = size('test/test.js');
    var c = size('test/class.js');
    //debugger;
    var res = yield[a, b, c];
    console.log(res);    // => [5171, 2090, 1477]
})();

//嵌套數組: 數組裏面還能夠嵌套數組. 下面代碼, 執行時至關於併發執行6個操做:
co(function *() {
    var a = [
        get('http://sina.com'),
        get('http://baidu.com'),
        get('http://163.com')
    ];
    var b = [
        get('http://sina.com'),
        get('http://yahoo.com'),
        get('http://ign.com')
    ];
    console.log(yield [a, b]);  // 數組裏嵌套數組, 將併發執行6個http.get!
})();

//對象: yield object, 相似數組, 不過還支持遞歸
co(function* yieldObject(){
    var user = yield { //yield在 object 外面, 則 object 內部的轉換器將併發執行
        name: {
            first: get('http://www.iciba.com/first'),   //這裏 object 裏層只是轉換器, 沒有yield
            last: get('http://www.iciba.com/last')      // 所以這兩個get也是併發執行
        }
    };  //最終合成 user object = {name: {first: "網頁內容1", last: "網頁內容2"}}
    console.log(user); // => {"name": {"first": "網頁內容1", "last": "網頁內容2"}}
})();
//若是對象內屬性值裏用yield, 則每一個yield是順序執行(非併發):
co(function *(){
    var user = {
        name: {
            first: yield  get('http://www.iciba.com/first'), //這裏 object 裏的屬性值經過 yield 獲取
            last: yield get('http://www.iciba.com/last')     // 所以這兩個get 是順序執行(非併發)
        }
    };
    console.log(user); // => {"name": {"first": "網頁內容1", "last": "網頁內容2"}}
})();

//性能: TJ的測試結果
//在我本機上順序執行 30,000 次 stat() 操做平均耗時 570ms, 而用 co() 執行一樣次數的 stat() 平均耗時 610ms,
//也就是說, yield 帶來的損耗幾乎能夠忽略不計.

//類(Class)裏的 generator & yield
//定義類的構造函數
function Class () {
    this.name = 'Class';
}
//定義類的類的成員函數 generator
Class.prototype.generator = function*(){
    var user = yield {  //yield 一個對象
        name: {
            first: get('http://www.iciba.com/first'),
            last: get('http://www.iciba.com/last')
        }
    };
    console.log(user);  // => {"name": {"first": "網頁內容1", "last": "網頁內容2"}}
    console.log('Class.run() end.')
};

//定義一個函數用於執行
Class.prototype.run = function() {
    co(this.generator)();   //用 co() 去執行
}
//建立類的實例
var obj = new Class();
//運行
obj.run();  // => {"name": {"first": "網頁內容1", "last": "網頁內容2"}}
相關文章
相關標籤/搜索