2009-11-27 5 views
19

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

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

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

+0

Тема устарела, но проверьте асинхронный режим: https://github.com/caolan/async – fider

ответ

24

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

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

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

EDIT: попробуйте добавить некоторые заявления о регистрации непосредственно перед окончанием каждой вложенной функции; это поможет проиллюстрировать, что порядок, в котором они заканчиваются, находится снаружи внутрь.

+0

Спасибо, что расчистили мои смешные мысли по этому вопросу. Мне все еще нужно работать с nodejs немного больше и делать еще несколько тестов (например, ведение журнала, которые вы предлагаете), чтобы убедить себя, что я не создаю горы областей или что-то в этом роде, но это шаг в этом направление. еще раз спасибо – Maciek

0

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

1

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

3

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

usefile.ЯШ:

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

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

Смежные вопросы