Управление большим количеством рекурсии обратного вызова в 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..
});
Я ожидаю написания кода, который сделает много операции ввода-вывода, так что я буду писать!--4-->много обратные вызовы. Мне вполне комфортно использовать обратные вызовы, но я беспокоюсь обо всех рекурсия. Есть ли опасность столкнуться с слишком большой рекурсией и продуть стек где-нибудь? Если я сделаю тысячи отдельных записей в хранилище ключей с тысячами обратных вызовов, моя программа в конечном итоге рухнет?
Я неправильно понимаю или недооцениваю воздействие? Если нет, есть ли способ обойти это, все еще используя стиль кодирования обратного вызова Nodejs?
6 ответов
ни один из кода, который вы показываете, не использует рекурсию. Когда вы звоните useFile
Он называет posix.stat()
, который возвращает, и useFile
завершает работу по мере ее завершения. В какое-то более позднее время, когда вызов posix.stat()
завершено в базовой системе и результаты доступны, функция обратного вызова, которую вы добавили для этого, будет выполнена. Это зовет posix.open()
, а затем завершается по мере его завершения. После успешного открытия файла функция обратного вызова для это будет выполняться, назвав posix.read()
, а затем завершится, поскольку он тоже побежал к завершению. Наконец, когда результаты чтения будут доступны, будет выполнена самая внутренняя функция.
важным моментом является то, что каждая функция запускается до завершения, так как вызовы posix.*()
функции не блокируются: то есть они возвращаются немедленно, вызвав некоторую магию, которая будет запущена в базовой системе. Таким образом, каждая из ваших функций завершается, а затем событие приведет к выполнению следующей функции; но ни в какой момент не существует рекурсии.
вложенная структура кода может создать впечатление, что материал внутри должен будет закончить, прежде чем материал снаружи сможет добраться до своей конечной точки. Но в этом стиле асинхронного событийного программирования имеет смысл видеть вложенность в терминах вглубь => происходит-позже, чем.
EDIT: попробуйте сразу добавить некоторые операторы регистрации перед окончанием каждой вложенной функции; это поможет проиллюстрировать, что порядок, в котором они завершаются, извне внутрь.
тот же пример, с добавленным выходом отладки (см. ниже для вывода):
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. Если вы столкнетесь с проблемами глубины рекурсии (как указывает Никфиц, вам это не грозит), вы можете часто переписывать свой код, чтобы использовать интервальный таймер.