了解具有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

这个项目我一直很难过。 会爱任何援助!

这是我想要完成的:

  1. 大量使用带有inheritance的John Resig的JavaScript Class支持修改版本的类
  2. 使用生成器获得对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.readdirfs.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因为像我这样返回一个函数是一种丑陋。

有了thunkco库,你可以在不使用示例函数的情况下执行上述操作:

 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循环来生成值,并且通过nextStepcallback来继续。 这段代码庞大而且不可读。

我们来介绍一下asynchronous。 在这种情况下,状态机的下一步的继续将不是由循环驱动for of ,而是由一些外部事件驱动的。 我将使用定时器间隔作为事件的来源,但也可能是Node.js操作完成callback或承诺parsingcallback。

这个想法是显示它如何工作,而不使用任何外部库 (如QBluebirdCo等)。 没有什么能够阻止发生器自我驱动到下一步,这就是下面的代码所做的。 一旦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); }); 

一旦你理解了这个概念,你可以继续使用承诺作为生成器的包装器,这将其带到下一个强大的层面。