在Nodejs中pipe理大量的callbackrecursion

在Nodejs中,几乎没有阻塞的I / O操作。 这意味着几乎所有的nodejs IO代码都涉及很多callback。 这适用于从数据库,文件,进程等读写数据,这是一个典型的例子:

var useFile = function(filename,callback){ posix.stat(filename).addCallback(function (stats) { posix.open(filename, process.O_RDONLY, 0666).addCallback(function (fd) { posix.read(fd, stats.size, 0).addCallback(function(contents){ callback(contents); }); }); }); }; ... useFile("test.data",function(data){ // use data.. }); 

我期待写代码将做许多 IO操作,所以我期望写很多callback。 我很喜欢使用callback,但我担心所有的recursion。 我是否有陷入过度recursion的危险,并在某个地方吹过堆栈? 如果我用成千上万的callback函数向我的键值存储进行数千次单独写入,我的程序是否会最终崩溃?

我是误解还是低估了影响? 如果没有,有没有办法解决这个问题,同时仍然使用Nodejs的callback编码风格?

你显示的代码都没有使用recursion。 当你调用useFile它会调用posix.stat() ,它会返回,并且useFile会在运行完成时终止。 稍后,当posix.stat()的调用在底层系统中完成并且结果可用时,将为您添加的callback函数执行。 调用posix.open() ,然后在运行完成时终止。 一旦文件成功打开,该函数的callback函数将执行,调用posix.read() ,然后终止,因为它也已经运行完成。 最后,当读取结果可用时,最内层的函数将被执行。

重要的一点是,每个函数都会运行完成,因为对posix.*()函数的调用是非阻塞的:也就是说,它们立即返回,导致在底层系统中启动一些魔法。 所以你的每个函数都会终止,随后一个事件会导致下一个函数的执行。 但没有任何recursion。

代码的嵌套结构可以给人一种印象,即内部的东西在外部的东西可以到达自己的终点之前必须完成。 但是在这种asynchronous事件驱动的编程风格中, 更深层次地看到嵌套是更合理的

编辑:尝试在每个嵌套函数结束之前立即添加一些日志logging语句; 这将有助于说明他们完成的顺序是从外部开始的。

同样的例子,添加了debugging输出(参见下面的输出):

usefile.js:

 var sys = require("sys"), posix = require("posix"); var useFile = function(filename,callback){ posix.stat(filename).addCallback(function (stats) { posix.open(filename, process.O_RDONLY, 0666).addCallback(function (fd) { posix.read(fd, stats.size, 0).addCallback(function(contents){ callback(contents); sys.debug("useFile callback returned"); }); sys.debug("read returned"); }); sys.debug("open returned"); }); sys.debug("stat returned"); }; useFile("usefile.js",function(){}); 

输出:

 DEBUG: stat returned DEBUG: open returned DEBUG: read returned DEBUG: useFile callback returned 

你可以试试

http://github.com/creationix/do

或像我一样滚动自己的。 不要在意现在丢失的error handling(只是忽略);)

 var sys = require('sys'); var Simplifier = exports.Simplifier = function() {} Simplifier.prototype.execute = function(context, functions, finalFunction) { this.functions = functions; this.results = {}; this.finalFunction = finalFunction; this.totalNumberOfCallbacks = 0 this.context = context; var self = this; functions.forEach(function(f) { f(function() { self.totalNumberOfCallbacks = self.totalNumberOfCallbacks + 1; self.results[f] = Array.prototype.slice.call(arguments, 0); if(self.totalNumberOfCallbacks >= self.functions.length) { // Order the results by the calling order of the functions var finalResults = []; self.functions.forEach(function(f) { finalResults.push(self.results[f][0]); }) // Call the final function passing back all the collected results in the right order finalFunction.apply(self.context, finalResults); } }); }); } 

一个简单的例子使用它

 // Execute new simplifier.Simplifier().execute( // Context of execution self, // Array of processes to execute before doing final handling [function(callback) { db.collection('githubusers', function(err, collection) { collection.find({}, {limit:30}, function(err, cursor) { cursor.toArray(function(err, users) { callback(users); }) }); }); }, function(callback) { db.collection('githubprojects', function(err, collection) { collection.find({}, {limit:45, sort:[['watchers', -1]]}, function(err, cursor) { cursor.toArray(function(err, projects) { callback(projects); }) }); }); } ], // Handle the final result function(users, projects) { // Do something when ready } ); 

你的东西很好。 我在Express中执行recursion调用以遵循HTTPredirect,但是你所做的是“遍历”而不是recursion

也可以看看github上的'step'( http://github.com/creationix/step )或'flow-js'。 这可以让您以更自然的风格编写callbackstream程。 这也将清楚地表明没有recursion正在进行。

与任何JavaScript一样,可以使用Node.js进行recursion调用。 如果你遇到recursion深度问题(就像NickFitz指出的那样,你似乎没有这个危险),你可以经常重写代码来使用间隔计时器。