






















2014-11-10 22:02 莫耶 阅读(1218) 评论() 收藏 举报
原文地址:http://www.moye.me/2014/11/10/ecmascript-6-generator/
老听人说 koa大法好,这两天我也赶了把时髦:用 n 安上了node 0.11.12,下了个koa开启harmony模式试水。在一系列文档和贴子的教育下,大概认识到:
在接触 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的中间件队列中{ value: obj, done:true/false }对象:value是执行结果值,done指示迭代是否完成// 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;
}
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;
}
function(fn) {
fs.readFile(dir, fn);
}
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();
用意很明显:
问题也很明显,业务代码(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/
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。