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"}}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏博岩Java大讲堂

多线程--同步与锁

2353
来自专栏北京马哥教育

搞定Linux Shell文本处理工具,看完这篇集锦就够了

Linux Shell是一种基本功,由于怪异的语法加之较差的可读性,通常被Python等脚本代替。既然是基本功,那就需要掌握,毕竟学习Shell脚本的过程中,还...

3222
来自专栏IT可乐

Spring详解(三)------DI依赖注入

  上一篇博客我们主要讲解了IOC控制反转,也就是说IOC 让程序员不在关注怎么去创建对象,而是关注与对象创建之后的操作,把对象的创建、初始化、销毁等工作交给s...

2005
来自专栏ImportSource

JVM中的“同步”到底是怎么实现的?

JVM中的Synchronization是使用monitor entry和exit来实现的。不管是显式的还是隐式的。显式的是通过使用monitorenter和m...

2885
来自专栏精讲JAVA

OutOfMemoryError异常系列之方法区溢出和运行时常量溢出池溢出

按照虚拟机的内存分配,运行时常量池属于方法区,所以今天在这一起讲了,大家都知道1.7的虚拟机规范出来以后,有个很重要的一点就是去永久代。今天我们...

23510
来自专栏技术博客

Asp.net MVC Jquery提交后乱码问题

最近在处理MVC时,遇到要将特殊字符,或者XML格式的数据传递到后台,但是后台解析发现无法识别,处理有误。

1762
来自专栏Spark学习技巧

JAVA之ClassLoader

JAVA基础系列之ClassLoader 一,Java类的加载、链接与初始化 1,加载:查找并加载类的二进制数据 • 通过一个类的全限定名来获取定义此类的二进制...

2209
来自专栏测试开发架构之路

堆和栈的区别

一、预备知识—程序的内存分配          一个由C/C++编译的程序占用的内存分为以下几个部分     1、栈区(stack)— 由编译器自动分配释放,存...

2878
来自专栏积累沉淀

Java设计模式(八)----代理模式

代理模式 1.生活中: 代理就是一个人或者一个组织代表其他人去做一件事的现实生活中的。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对...

23310
来自专栏Java 源码分析

Bootstrap 源码分析

Netty 源码分析: Bootstrap 1. 结构 先看一个这个类的类层次结构, ? 好,这个结构还是比较明晰的,然后看他的主要字段,因为这些字段比较重...

3042

扫码关注云+社区

领取腾讯云代金券