Управление большим количеством рекурсии обратного вызова в Nodejs

StackOverflow https://stackoverflow.com/questions/1809619

Вопрос

В 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?

Это было полезно?

Решение

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

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

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

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

Другие советы

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

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

или сверните свое, как я. Не берите в голову пропущенную обработку ошибок (просто игнорируйте это);)

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, чтобы следовать HTTP-перенаправлениям, но то, что вы делаете, - это "обход" а не рекурсия

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top