了解具有fs模块的Node JS生成器
我一直对Node JS非常兴奋。 我终于决定closures并编写一个testing项目,以了解Node最新的Harmony构build中的生成器。
这是我非常简单的testing项目:
https://github.com/kirkouimet/project-node
要运行我的testing项目,您可以轻松地从Github中提取文件,然后运行它:
node --harmony App.js
这是我的问题 – 我似乎无法让节点的asynchronousfs.readdir方法与内置的生成器运行。 其他项目,如银河和暂停似乎能够做到这一点。
这是我需要修复的代码块。 我想能够实例化FileSystemtypes的对象并调用其上的.list()方法:
https://github.com/kirkouimet/project-node/blob/4c77294f42da9e078775bb84c763d4c60f21e1cc/FileSystem.js#L7-L11
FileSystem = Class.extend({ construct: function() { this.currentDirectory = null; }, list: function*(path) { var list = yield NodeFileSystem.readdir(path); return list; } });
我需要提前做些什么来将节点的fs.readdir转换成一个生成器?
一个重要的注意事项是,我正在parsing创build的所有类函数。 这使我可以处理发电机的function,而不像普通的function
https://github.com/kirkouimet/project-node/blob/4c77294f42da9e078775bb84c763d4c60f21e1cc/Class.js#L31-L51
这个项目我一直很难过。 会爱任何援助!
这是我想要完成的:
- 大量使用带有inheritance的John Resig的JavaScript Class支持修改版本的类
- 使用生成器获得对Node的asynchronous调用的内联支持
编辑
我试图实现你的例子function,我遇到了一些麻烦。
list: function*(path) { var list = null; var whatDoesCoReturn = co(function*() { list = yield readdir(path); console.log(list); // This shows an array of files (good!) return list; // Just my guess that co should get this back, it doesn't })(); console.log(whatDoesCoReturn); // This returns undefined (sad times) // I need to use `list` right here return list; // This returns as null }
首先,最重要的是要有一个良好的模型在你的脑子里究竟是一个发电机。 生成器函数是一个函数,它返回一个生成器对象,当你在其上调用.next()
时,生成器对象将在生成器函数中逐步执行yield
语句。
鉴于这种描述,你应该注意到asynchronous行为没有被提及。 发电机上的任何动作都是同步的。 您可以立即运行到第一个yield
,然后执行setTimeout
,然后调用.next()
转到下一个yield
,但是setTimeout
会导致asynchronous行为,而不是生成器本身。
所以让我们来fs.readdir
。 fs.readdir
是一个asynchronous函数,在自己的生成器中使用它将不起作用。 让我们看看你的例子:
function * read(path){ return yield fs.readdir(path); } var gen = read(path); // gen is now a generator object. var first = gen.next(); // This is equivalent to first = fs.readdir(path); // Which means first === undefined since fs.readdir returns nothing. var final = gen.next(); // This is equivalent to final = undefined; // Because you are returning the result of 'yield', and that is the value passed // into .next(), and you are not passing anything to it.
希望它能让你更清楚地知道你还在同步调用readdir
,而且你没有传递任何callback,所以它可能会抛出一个错误或者其他的东西。
那么如何从发电机中获得良好的行为呢?
通常这是通过让生成器产生一个特殊的对象来实现的,它代表了readdir
在实际计算之前的结果。
对于(不切实际的)例子, yield
一个函数是产生代表值的简单方法。
function * read(path){ return yield function(callback){ fs.readdir(path, callback); }; } var gen = read(path); // gen is now a generator object. var first = gen.next(); // This is equivalent to first = function(callback){ ... }; // Trigger the callback to calculate the value here. first(function(err, dir){ var dirData = gen.next(dir); // This will just return 'dir' since we are directly returning the yielded value. // Do whatever. });
真的,你会希望这种types的逻辑继续调用生成器,直到所有yield
调用完成,而不是硬编码每个调用。 现在要注意的主要事情是,生成器本身看起来是同步的, read
函数之外的所有东西都是超级通用的。
你需要某种types的生成器包装函数来处理这个yield值过程,而你的suspend
的例子就是这样做的。 另一个例子是co
。
“返回代表值的方法”的标准方法是返回一个promise
或一个thunk
因为像我这样返回一个函数是一种丑陋。
有了thunk
和co
库,你可以在不使用示例函数的情况下执行上述操作:
var thunkify = require('thunkify'); var co = require('co'); var fs = require('fs'); var readdir = thunkify(fs.readdir); co(function * (){ // `readdir` will call the node function, and return a thunk representing the // directory, which is then `yield`ed to `co`, which will wait for the data // to be ready, and then it will start the generator again, passing the value // as the result of the `yield`. var dirData = yield readdir(path, callback); // Do whatever. })(function(err, result){ // This callback is called once the synchronous-looking generator has returned. // or thrown an exception. });
更新
你的更新仍然有一些困惑。 如果你希望你的list
函数是一个生成器,那么你将需要在 list
外使用co
,无论你在哪里调用它。 co
内部的所有东西都应该是基于生成器的, co
以外的所有东西都应该是基于callback的。 co
不会使list
自动asynchronous。 co
用于将基于生成器的asynchronousstream量控制转换为基于callback的stream量控制。
例如
list: function(path, callback){ co(function * (){ var list = yield readdir(path); // Use `list` right here. return list; })(function(err, result){ // err here would be set if your 'readdir' call had an error // result is the return value from 'co', so it would be 'list'. callback(err, result); }) }
@loganfsmyth已经为您的问题提供了一个很好的答案 。 我的答案的目标是帮助您了解JavaScript生成器如何工作,因为这是正确使用它们的一个非常重要的步骤。
发电机实现一个状态机 ,这个概念本身并不新鲜。 最新的是,生成器允许使用熟悉的JavaScript语言结构(例如, if
try/catch
)来实现状态机而不放弃线性代码stream。
生成器的最初目标是生成一系列数据,这与asynchronous无关。 例:
// with generator function* sequence() { var i = 0; while (i < 10) yield ++i * 2; } for (var j of sequence()) console.log(j); // without generator function bulkySequence() { var i = 0; var nextStep = function() { if ( i >= 10 ) return { value: undefined, done: true }; return { value: ++i * 2, done: false }; } return { next: nextStep }; } for (var j of bulkySequence()) console.log(j);
第二部分( bulkySequence
)展示了如何以传统的方式实现相同的状态机,没有生成器。 在这种情况下,我们不再能够使用while
循环来生成值,并且通过nextStep
callback来继续。 这段代码庞大而且不可读。
我们来介绍一下asynchronous。 在这种情况下,状态机的下一步的继续将不是由循环驱动for of
,而是由一些外部事件驱动的。 我将使用定时器间隔作为事件的来源,但也可能是Node.js操作完成callback或承诺parsingcallback。
这个想法是显示它如何工作,而不使用任何外部库 (如Q
, Bluebird
, Co
等)。 没有什么能够阻止发生器自我驱动到下一步,这就是下面的代码所做的。 一旦asynchronous逻辑的所有步骤完成(10个计时器滴答),就会调用doneCallback
。 请注意, 我不会在这里返回任何有意义的数据 。 我只是用它来暂停和恢复执行:
function workAsync(doneCallback) { var worker = (function* () { // the timer callback drivers to the next step var interval = setInterval(function() { worker.next(); }, 500); try { var tick = 0; while (tick < 10 ) { // resume upon next tick yield null; console.log("tick: " + tick++); } doneCallback(null, null); } catch (ex) { doneCallback(ex, null); } finally { clearInterval(interval); } })(); // initial step worker.next(); } workAsync(function(err, result) { console.log("Done, any errror: " + err); });
最后,让我们创build一系列事件:
function workAsync(doneCallback) { var worker = (function* () { // the timer callback drivers to the next step setTimeout(function() { worker.next(); }, 1000); yield null; console.log("timer1 fired."); setTimeout(function() { worker.next(); }, 2000); yield null; console.log("timer2 fired."); setTimeout(function() { worker.next(); }, 3000); yield null; console.log("timer3 fired."); doneCallback(null, null); })(); // initial step worker.next(); } workAsync(function(err, result) { console.log("Done, any errror: " + err); });
一旦你理解了这个概念,你可以继续使用承诺作为生成器的包装器,这将其带到下一个强大的层面。