Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

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

Ответ 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
  }
);

Ответ 4

Ваши вещи в порядке. Я выполняю рекурсивные вызовы в Express, чтобы отслеживать переадресацию HTTP, но что вы делаете "обход", а не рекурсия

Ответ 5

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

Ответ 6

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