18

在 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 的回调编码风格的同时解决这个问题?

4

6 回答 6

24

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

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

代码的嵌套结构给人的印象是内部的东西必须在外部的东西到达自己的终点之前完成。但是在这种异步事件驱动的编程风格中,从更深的 => 发生-延迟-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.

于 2009-11-27T16:42:11.533 回答
3

Same example, with debug output added (see below for output):

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(){});

Output:

DEBUG: stat returned
DEBUG: open returned
DEBUG: read returned
DEBUG: useFile callback returned
于 2010-01-21T12:57:31.133 回答
3

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) ;)

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);
      }
    });
  });
}

And a simple example using it

// 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
  }
);
于 2010-03-14T01:35:38.403 回答
1

Your stuff is fine. I do recursive calls in Express to follow HTTP redirects, but what your doing is "traversal" and not recursion

于 2009-12-18T01:59:12.020 回答
1

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.

于 2010-06-24T18:48:06.990 回答
0

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.

于 2009-12-01T22:00:36.013 回答