在Nodejs中,几乎没有阻塞I / O操作。这意味着几乎所有nodejs IO代码都涉及许多回调。这适用于对数据库,文件,进程等的读写操作。典型的例子如下:
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操作的代码,因此我希望编写许多回调。我对使用回调很满意,但我担心所有的递归。我是否有可能遇到过多的递归并在某个地方吹过堆栈?如果我通过数千次回调对我的键值存储进行数千次单独写入操作,我的程序最终会崩溃吗?
我误解或低估了影响吗?如果没有,有没有办法解决这个问题,同时仍然使用Nodejs的回调编码风格?
答案 0 :(得分:24)
您显示的代码都没有使用递归。当您致电useFile
时,它会调用posix.stat()
,并返回useFile
,因为它已经运行完成。稍后,当对posix.stat()
的调用已完成在基础系统中并且结果可用时,您为此添加的回调函数将被执行。调用posix.open()
,然后在运行完成时终止。文件成功打开后, 的回调函数将执行,调用posix.read()
,然后终止,因为它也已运行完成。最后,当读取结果可用时,将执行最里面的函数。
重要的一点是每个函数都运行完成,因为对posix.*()
函数的调用是非阻塞的:也就是说,它们会立即返回,导致在底层系统中启动一些魔法。因此,每个函数都会终止,稍后事件将导致下一个函数执行;但是没有任何递归。
代码的嵌套结构可以给人一种印象,即在外面的东西可以到达自己的终点之前,内部的东西必须完成。但是在这种异步事件驱动编程风格中,根据 deep =>来看待嵌套更有意义。发生晚于如下
编辑:尝试在每个嵌套函数结束之前立即添加一些日志语句;这将有助于说明它们完成的顺序来自外部。
答案 1 :(得分:3)
相同的例子,添加了调试输出(参见下面的输出):
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
答案 2 :(得分:3)
你可以尝试
http://github.com/creationix/do
或像我一样滚动你自己。别介意暂时缺少错误处理(只是忽略它);)
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
}
);
答案 3 :(得分:1)
你的东西很好。我在Express中进行递归调用以跟踪HTTP重定向,但你做的是“遍历”而不是递归
答案 4 :(得分:1)
另请参阅github上的'step'(http://github.com/creationix/step)或'flow-js'。这使您可以以更自然的方式编写回调流。这也将表明没有递归。
答案 5 :(得分:0)
与任何JavaScript一样,可以使用Node.js进行递归调用。如果你遇到了递归深度问题(正如NickFitz指出的那样,你似乎没有这种危险),你可以经常重写你的代码来使用间隔计时器。