[Node.js] ECMAScript 6中的生成器及koa小析
原文地址:http://www.moye.me/2014/11/10/ecmascript-6-generator/
引子
老听人说 koa大法好,这两天我也赶了把时髦:用 n 安上了node 0.11.12,下了个koa开启harmony模式试水。在一系列文档和贴子的教育下,大概认识到:
- koa 是TJ大神主导的新一代Web框架
- koa 的中间件基于ES6的生成器函数(function *)形式
- koa的核心流程库是 co,它能很好的解决Pyramid of Doom问题
在接触 Node.js 前,由于有过 Python编程的经验,我对生成器是个什么东西已经是很清楚了。我真正感兴趣的是:它是怎么被用来优化回调嵌套的。
“为何这么屌”
在 KOA框架为何这么屌 一文中有这么一段(片断1):
var fs = require('fs');
var app = require('koa')();
var readFile = function(dir) {
return function(fn) {
fs.readFile(dir, fn);
}
}
app.use(function* () {
var arr = yield ['1.txt', '2.txt', '3.txt'].map(function(path) {
return readFile(path);
});
this.body = arr.join(',');
})
app.listen(8000);
这段代码很好的演示了koa是如何利用生成器函数(function*(),函数的constructor.name === 'GeneratorFunction' )来串行化异步回调的,它的执行流程:
function*(){...}
被做为生成器函数push到了koa的中间件队列中- koa使用co框架,对这个生成器函数进行调用执行;执行生成器函数并不立即执行函数体,而是生成生成器(generator)实例——同时,生成器可视为遵循迭代器协议的实例,每次调用迭代器的next(),都会返回一个
{ value: obj, done:true/false }
对象:value是执行结果值,done指示迭代是否完成 - 调用生成器实例的next()方法将启动函数体内部的执行流,直到出现yield时被挂起,那么下一次的next()将会给yield返回执行的结果值,并挂起在下一个yield出现处;可以理解为,yield总是返回上一次next()的结果值,如果next()有参数,yield将返回这个参数值(异步回调有机会注入执行结果)
- 观察上面代码片段我们注意到,readFile的回调处理函数fn代码中并未提供,那么koa或者说co是怎么处理这个回调函数的呢?在 koa(0.13.0)自带的co源码(line:84)中,可以看到如下片断(片断2):
// normalize
ret.value = toThunk(ret.value, ctx);
// run
if ('function' == typeof ret.value) {
var called = false;
try {
ret.value.call(ctx, function(){
if (called) return;
called = true;
next.apply(ctx, arguments);
});
} catch (e) {
setImmediate(function(){
if (called) return;
called = true;
next(e);
});
}
return;
} - toThunk 会根据 yield返回的表达式转换成标准函数(片断3):
function toThunk(obj, ctx) {
if (isGeneratorFunction(obj)) {
return co(obj.call(ctx));
}
if (isGenerator(obj)) {
return co(obj);
}
if (isPromise(obj)) {
return promiseToThunk(obj);
}
if ('function' == typeof obj) {
return obj;
}
if (isObject(obj) || Array.isArray(obj)) {
return objectToThunk.call(ctx, obj);
}
return obj;
} - 在片断1 中,生成器函数首先返回的是一个生成器;然后,yield 结合map会返回三个function对象,即高阶函数readFile返回的function:
function(fn) {
fs.readFile(dir, fn);
} - 片断2 根据 片断3 返回的类型,对 function 进行了call调用,并提供了回调函数:将arguments通过
next.apply(ctx, arguments);
巧妙的进行传递。如前所述,next()如果提供了参数,yield得到的结果值就是这个参数,回调结果由此而来。
到底是谁屌
如果看官看完上面那几段还没晕,那当然是您最屌:) ——我的表述能力确实不足以很清晰的道出框架的玄机,但在我看来,真正屌的是ES6 Generator机制本身。
暂时放下 co 框架,把 片断1 稍加改造(片断4):
var fs = require('fs');
var path = require('path'); var readFile = function (dir) {
return function (fn) {
fs.readFile(dir, {encoding: 'utf8', flag: 'r'}, fn);
};
}; function *readFileGeneratorFunction(path, cb){
console.log(yield readFile(path)(cb));
} var readFileIterator = readFileGeneratorFunction('testDate.js', callback);
function callback(err, data){
readFileIterator.next(data);
}
readFileIterator.next();
用意很明显:
- 这个readFileGeneratorFunction就是个生成器函数,执行它返回一个生成器(迭代器)
- 高阶函数返回的function,在生成器函数执行时指定了回调
- next触发执行
- 回调完成时,next(data)携带结果值触发yield
问题也很明显,业务代码(GeneratorFunction中的yield) 需要前置于流程控制(callback),这不科学。抽象一下,可以提供一个生成器函数的执行函数:
var slice = Array.prototype.slice;
function run(generatorFunction) {
try {
var generatorItr = generatorFunction(callback);
function callback(err, res) {
if(err)
generatorItr.throw(err);
else {
var args = slice.call(arguments, 1);
res = args.length > 1 ? args : res;
generatorItr.next(res);
}
}
generatorItr.next();
}
catch (e){
console.log(e.message | "I'm died.");
}
}; 测试一下: run(function* rfGenFunc(cb) {
console.log('first');
console.log(yield readFile('1.txt')(cb));
console.log('second');
console.log(yield readFile('2.txt')(cb));
});
执行结果:
小结
本文仅对Generator的next()应用进行了简单的描述(其实它还有更多内容如:throw/send/close),抛砖引玉罢了。至于生成器特性,目前仍处于 ECMAScript 6 规范草案中,如MDN所言:请谨慎使用 :)
更多文章请移步我的blog新地址: http://www.moye.me/
[Node.js] ECMAScript 6中的生成器及koa小析的更多相关文章
- Elasticsearch.js 发布 —— 在Node.js和浏览器中调用Elasticsearch(1)
继PHP.Ruby.Python和Perl之后,Elasticsearch最近发布了Elasticsearch.js,Elasticsearch的JavaScript客户端库.可以在Node.js和浏 ...
- Elasticsearch.js 发布 —— 在Node.js和浏览器中调用Elasticsearch
继PHP.Ruby.Python和Perl之后,Elasticsearch最近发布了Elasticsearch.js,Elasticsearch的JavaScript客户端库.可以在Node.js和浏 ...
- Node.js开发指南中的例子(mysql版)
工作原因需要用到nodejs,于是找到了<node.js开发指南>这本书来看看,作者BYVoid 为清华大学计算机系的高材生,年纪竟比我还小一两岁,中华地广物博真是人才辈出,佩服. 言归正 ...
- node.js获取url中的各个参数
实例代码test.js var http=require('http'); var url=require('url'); var querystring=require('querystring') ...
- 高效使用 JavaScript 闭包,避免 Node.js 应用程序中的内存泄漏
在 Node.js 中,广泛采用不同形式的闭包来支持 Node 的异步和事件驱动编程模型.通过很好地理解闭包,您可以确保所开发应用程序的功能正确性.稳定性和可伸缩性. 闭包是一种将数据与处理数据的代码 ...
- Node.js向MongoDB中插入并查询数据
首先必须要保持Node.js与MongoDB保持连接 具体教程见:Node.js连接MongoDB数据库步骤 插入数据步骤如下 node项目文件如下:在routes文件夹下新建insert.js文件, ...
- Koa与Node.js开发实战(2)——使用Koa中间件获取响应时间(视频演示)
学习架构: 在实战项目中,经常需要记录下服务器的响应时间,也就是从服务器接收到HTTP请求,到最终返回给客户端之间所耗时长.在Koa应用中,利用中间件机制可以很方便的实现这一功能.代码如下所示: 01 ...
- Node.js躬行记(19)——KOA源码分析(上)
本次分析的KOA版本是2.13.1,它非常轻量,诸如路由.模板等功能默认都不提供,需要自己引入相关的中间件. 源码的目录结构比较简单,主要分为3部分,__tests__,lib和docs,从名称中就可 ...
- Node.js躬行记(20)——KOA源码分析(下)
在上一篇中,主要分析了package.json和application.js文件,本文会分析剩下的几个文件. 一.context.js 在context.js中,会处理错误,cookie,JSON格式 ...
随机推荐
- MAC下的命令操作
打印环境变量:echo $PATH设置环境变量:export PATH=$PATH:/usr/local/git/bin应用:在git-scm下载git2.0.1版本,图形安装后,直接在终端敲giv ...
- 解决Visual Studio 2010/2012在调试时lock文件的方法
调试3dsmax插件,有一个避免每次修改插件代码都需要重启3dsmax的方法,就是将导出的核心代码写在一个独立的DLL中,然后在插件代码需要导出时LoadLibrary这个DLL,导出之后再FreeL ...
- java中获取比毫秒更为精确的时间
所以这里提醒做非常精确的时间统计的朋友,谨慎使用System.currentTimeMillis() . 在Java中可以通过System.currentTimeMillis()或者System.na ...
- Python LDAP中的时间戳转换为Linux下时间
(Get-ADUser zhangsan -Properties badpasswordtime).badpasswordtime返回值为:131172610187388712131172610187 ...
- shell变量注意事项
概念:变量赋值,变量替换,变量引用,命令替换 variable=22 echo variable 可以在同一行设置多个变量.例如 va1=good va2=chif va3=beijing #需 ...
- javascript提升复习
https://www.baidu.com/s?wd=JavaScript+%E9%A2%84%E8%A7%A3%E6%9E%90 http://www.cnblogs.com/HPNiuYear/a ...
- 第六章 - 图像变换 - 图像拉伸、收缩、扭曲、旋转[1] - 仿射变换(cvWarpAffine)
拉伸.收缩.扭曲.旋转是图像的几何变换,在三维视觉技术中大量应用到这些变换,又分为仿射变换和透视变换.仿射变换通常用单应性建模,利用cvWarpAffine解决密集映射,用cvTransform解决稀 ...
- Codeforces Round #182 (Div. 1)题解【ABCD】
Codeforces Round #182 (Div. 1)题解 A题:Yaroslav and Sequence1 题意: 给你\(2*n+1\)个元素,你每次可以进行无数种操作,每次操作必须选择其 ...
- Android Dagger依赖注入框架浅析
今天接触了Dagger这套android的依赖注入框架(DI框架).感觉跟Spring 的IOC差点儿相同吧.这个框架它的优点是它没有採用反射技术(Spring是用反射的),而是用预编译技术.因为基于 ...
- win10 Enable developer Mode
经过漫长的安装过程 win10终于装上了vs2015 rc- 写个小程序试试 结果提示: 根据提示打开 设置--更新--for developer 据说应该有这么个界面: 但是这个界面根本 ...