Управление большим количеством рекурсии в Nodejs - PullRequest
18 голосов
/ 27 ноября 2009

В Nodejs практически нет блокирующих операций ввода-вывода. Это означает, что почти весь код ввода-вывода nodejs включает много обратных вызовов. Это относится к чтению и записи в / из баз данных, файлов, процессов и т. Д. Типичным примером этого является:

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

Я ожидаю написания кода, который будет выполнять много операций ввода-вывода, поэтому я ожидаю написать много обратных вызовов. Мне вполне удобно использовать обратные вызовы, но я беспокоюсь обо всей рекурсии. Могу ли я столкнуться с слишком большим количеством рекурсии и где-нибудь пробиться через стек? Если я сделаю тысячи отдельных записей в свое хранилище значений ключей с тысячами обратных вызовов, произойдет ли сбой моей программы?

Я неправильно понимаю или недооцениваю воздействие? Если нет, есть ли способ обойти это, все еще используя стиль кодирования обратного вызова Nodejs?

Ответы [ 6 ]

24 голосов
/ 27 ноября 2009

Ни один из кодов, которые вы показываете, не использует рекурсию. Когда вы вызываете useFile, он вызывает posix.stat(), что возвращает, а useFile завершается, когда он завершается. Через некоторое время, когда вызов posix.stat() завершится в базовой системе и результаты станут доступны, будет добавлена ​​функция обратного вызова, добавленная для этого. Это вызывает posix.open(), а затем завершается по мере его завершения. Как только файл был успешно открыт, будет выполнена функция обратного вызова для , которую выполнит, вызвав posix.read(), и затем прекратит работу, поскольку она также запустилась до завершения. Наконец, когда результаты чтения станут доступны, будет выполнена самая внутренняя функция.

Важным моментом является то, что каждая функция выполняется до конца, так как вызовы функций posix.*() неблокируют: то есть они возвращаются немедленно, что вызвало запуск некоторой магии в базовой системе. Таким образом, каждая из ваших функций завершается, и позднее событие вызовет выполнение следующей функции; но ни в коем случае нет никакой рекурсии.

Вложенная структура кода может создать впечатление, что материал внутри должен будет закончить, прежде чем материал снаружи сможет достичь своей конечной точки. Но в этом стиле асинхронного программирования, управляемого событиями, имеет больше смысла видеть вложенность в терминах глубже => происходит позже, чем .

РЕДАКТИРОВАТЬ: попробуйте добавить некоторые операторы регистрации непосредственно перед концом каждой вложенной функции; это поможет проиллюстрировать, что порядок, в котором они выполняются, снаружи внутрь.

3 голосов
/ 14 марта 2010

Вы можете попробовать

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 голосов
/ 21 января 2010

Тот же пример, с добавленным выводом отладки (см. Вывод ниже):

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
1 голос
/ 24 июня 2010

Также взгляните на 'step' (http://github.com/creationix/step) или 'flow-js' на github. Это позволит вам записывать потоки обратного вызова в более естественном стиле. Это также прояснит, что рекурсия не происходит на.

1 голос
/ 18 декабря 2009

У тебя все хорошо. Я делаю рекурсивные вызовы в Express, чтобы следовать HTTP-перенаправлениям, но вы делаете «обход», а не рекурсию

0 голосов
/ 02 декабря 2009

Как и в любом JavaScript, с Node.js. можно совершать рекурсивные вызовы. Если вы сталкиваетесь с проблемами глубины рекурсии (как указывает NickFitz, вам, похоже, это не грозит), вы часто можете переписать свой код, чтобы использовать вместо него интервальный таймер.

...