在 Nodejs 中管理大量回调递归
在 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 的回调编码风格的同时解决这个问题?
In Nodejs, there are virtually no blocking I/O operations. This means that almost all nodejs IO code involves many callbacks. This applies to reading and writing to/from databases, files, processes, etc. A typical example of this is the following:
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..
});
I am anticipating writing code that will make many IO operations, so I expect to be writing many callbacks. I'm quite comfortable with using callbacks, but I'm worried about all the recursion. Am I in danger of running into too much recursion and blowing through a stack somewhere? If I make thousands of individual writes to my key-value store with thousands of callbacks, will my program eventually crash?
Am I misunderstanding or underestimating the impact? If not, is there a way to get around this while still using Nodejs' callback coding style?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
您显示的代码均未使用递归。当您调用
useFile
时,它会调用posix.stat()
,该函数返回,并且useFile
在运行完成时终止。稍后,当底层系统中对 posix.stat() 的调用完成并且结果可用时,您为此添加的回调函数将被执行。它调用 posix.open(),然后在运行完成时终止。文件成功打开后,that 的回调函数将执行,调用 posix.read(),然后终止,因为它也已运行完成。最后,当读取结果可用时,将执行最里面的函数。重要的一点是,每个函数都会运行完成,因为对 posix.*() 函数的调用是非阻塞的:也就是说,它们会立即返回,从而导致在底层系统。因此,每个函数都会终止,稍后一个事件将导致下一个函数执行;但在任何时候都不存在任何递归。
代码的嵌套结构会给人一种这样的印象:内部的内容必须先完成,外部的内容才能到达其自己的终点。但在这种异步事件驱动编程风格中,从更深层次的 => 角度看待嵌套更有意义。发生晚于。
编辑:尝试在每个嵌套函数结束之前添加一些日志语句;这将有助于说明它们完成的顺序是从外向内。
None of the code you show is using recursion. When you call
useFile
it callsposix.stat()
, which returns, anduseFile
terminates as it has run to completion. At some later time, when the call toposix.stat()
has completed within the underlying system and the results are available, the callback function you added for that will be executed. That callsposix.open()
, and then terminates as it has run to completion. Once the file has been successfully opened, the callback function for that will execute, callingposix.read()
, and will then terminate as it, too, has run to completion. Finally, when the results of the read are available, the innermost function will be executed.The important point is that each function runs to completion, as the calls to the
posix.*()
functions are non-blocking: that is, they return immediately, having caused some magic to be started off in the underlying system. So each of your functions terminates, and later an event will cause the next function to execute; but at no point is there any recursion.The nested structure of the code can give one the impression that the stuff inside will have to finish before the stuff outside can get to its own end point. But in this style of asynchronous event-driven programming it makes more sense to see the nesting in terms of deeper => happens-later-than.
EDIT: Try adding some logging statements immediately before the end of each nested function; this will help to illustrate that the order in which they complete is from the outside inwards.
相同的示例,添加了调试输出(输出见下文):
usefile.js:
输出:
Same example, with debug output added (see below for output):
usefile.js:
Output:
您可以尝试
http://github.com/creationix/do
或像我一样自行推出。现在不要介意缺少错误处理(只需忽略它);)
以及一个使用它的简单示例
You can try
http://github.com/creationix/do
or roll your own like I did. Never mind missing error handling for now (just ignore that) ;)
And a simple example using it
你的东西没问题。我在 Express 中进行递归调用以遵循 HTTP 重定向,但您所做的是“遍历”而不是递归
Your stuff is fine. I do recursive calls in Express to follow HTTP redirects, but what your doing is "traversal" and not recursion
另请查看“step”(http://github.com/creationix/step) 或github 上的“flow-js”。这使您可以以更自然的风格编写回调流。这也将表明没有发生递归。
Also take a look at 'step' (http://github.com/creationix/step) or 'flow-js' on github. This lets you write callback flows in a more natural style. This will also make it clear that there's no recursion going on.
与任何 JavaScript 一样,可以使用 Node.js 进行递归调用。如果您确实遇到了递归深度问题(正如 NickFitz 指出的那样,您似乎没有遇到这种危险),您通常可以重写代码以使用间隔计时器。
As with any JavaScript, it's possible to make recursive calls with Node.js. If you do run into recursion depth problems (as NickFitz points out, you don't seem to be in danger of that), you can often rewrite your code to use an interval timer instead.