Вы инициируют кучу асинхронных операций внутри for
цикла и ожидает все асинхронные операции, которые будут выполняться, когда цикл for
делается. Но на самом деле ни один из них еще не сделан, поэтому ваш массив doc.comments
еще не заселен. Вы пытались использовать его до того, как оно было заполнено, поэтому вы видите, что оно определено там, где вы пытались его использовать.
Лучший способ решить эту задачу - научиться использовать обещания, а затем запускать несколько запросов с помощью чего-то типа Promise.map()
или ES6 Promise.all()
, а затем сообщить механизму обещания, когда все запросы будут выполнены.
Короткие преобразования базы данных звонков через использовать Promises, вы можете передать-код, чтобы знать, когда все делается следующим образом:
Ручной закодированы Ответный Реализация
router.get('/:id', function (req, res, next) {
List.findOne({listurl: req.params.id}, function (err, doc) {
var doneCnt = 0;
if (!err && doc != null) {
for (var i = 0; i < doc.comments.length; i++) {
(function(index) {
User.findOne({Name: doc.comments[i].commenter}, function (err, data) {
++doneCnt;
if (err) {
// need some form of error handling here
doc.comments[index].gvUrl = "";
} else {
if (data) {
doc.comments[index].gvUrl = gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'});
} else {
doc.comments[index].gvUrl = 'noGravs';
}
}
// if all requests are done now
if (doneCnt === doc.documents.length) {
res.render('list', {
appTitle: doc.Title,
list: doc
});
}
});
})(i);
}
}
else {
res.status(404).render('404', {appTitle: "Book not found"});
}
});
}
Этот код распознает следующие действия о ваших асинхронных операциях:
- Вы запускаете несколько асинхронных операций
User.findOne()
s в петле.
- Эти асинхронные операции могут быть завершены в любом порядке.
- Эти операции асинхронного сканирования не будут завершены, когда цикл будет выполнен. Весь цикл, выполненный, инициирует операции. Они все закончится некоторое неопределенное время позже.
- Чтобы узнать, когда все операции асинхронизации выполнены, он поддерживает счетчик, чтобы подсчитать, сколько из них выполнено и отображает страницу, когда счетчик достигает количества запущенных в данный момент запросов. Это «ручной» способ узнать, когда все они сделаны.
Bluebird Promise Реализация
Вот как это может работать, используя Bluebird Обещания библиотеки и конвертирования операции базы данных для поддержки Обещания:
var Promise = require('bluebird');
// promisify the methods of the List and User objects
var List = Promise.promisifyAll(List);
var User = Promise.promisifyAll(User);
router.get('/:id', function (req, res, next) {
List.findOneAsync({listurl: req.params.id}).then(function(doc) {
if (!doc) {
throw "Empty Document";
}
return Promise.map(doc.comments, function(item, index, length) {
return User.findOneAsync({Name: item.commenter}).then(function(data) {
if (data) {
item.gvUrl = gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'});
} else {
item.gvUrl = 'noGravs';
}
});
}).then(function() {
return doc;
});
}).then(function(doc) {
res.render('list', {
appTitle: doc.Title,
list: doc
});
}).catch(function(err) {
res.status(404).render('404', {appTitle: "Book not found"});
});
}
Вот как это работает:
- Загрузить библиотеку обещаний Bluebird
- Добавьте многообещающие методы в объекты
User
и List
, чтобы добавить новую версию каждого метода, который возвращает обещание.
- Звоните
List.findOneAsync()
. Суффикс "Async"
имени метода представляет новый метод, добавленный .promisifyAll()
.
- Если нет
doc
, тогда бросок, который отклонит обещание, будет обработан в .catch()
в конце. Обещания - это асинхронный сброс, который очень удобен для обработки ошибок async.
- Звоните
Promise.map()
на номер doc.comments
. Это автоматически приведет к итерации массива doc.comments
и вызовет итератор для каждого элемента массива (аналогично Array.prototype.map()
, за исключением того, что здесь он собирает все обещания, возвращаемые итератором, и возвращает новое обещание, которое разрешается, когда устраняются все основные обещания. Кстати, это позволяет всем итераторам работать параллельно и говорит вам, когда все итераторы сделаны.
- итератор вызывает
User.findOneAsync()
и устанавливает значение doc.comments[index].gvUrl
с результатом.
- Там есть дополнительный
.then()
обработчик на Promise.map()
только к измените разрешенное значение этого обещания как объект doc
, чтобы мы могли получить это от внешних обработчиков.
- Для успеха из внешнего обещания визуализируйте.
- Для получения информации об ошибке от внешнего обещания укажите страницу 404. Имейте в виду, что любые отклоненные обещания в любой точке всей этой схемы будут распространяться и быть отклонением на верхнем уровне. Это автоматическое распространение асинхронных ошибок в обещаниях чрезвычайно полезно.
ES6 Promise Реализация
Это может быть сделано с прямыми ES6 обещаниями без Bluebird библиотеки обещания, но вы должны сделать несколько вещей вручную:
- Вам нужно будет обезопасить операцию
List.findOne()
.
- Вам нужно будет проделать операцию
User.findOne()
.
- Вы должны выполнить обычную
doc.comments.map()
итерацию и собрать каждое отдельное обещание в массив, а затем использовать Promise.all()
на этом массиве вместо того, чтобы позволить Promise.map()
сделать все это за вас.
Вот код:
// manually promisify findOne
List.findOneAsync = function(queryObj) {
return new Promise(function(resolve, reject) {
List.findOne(queryObj, function(err, data) {
if (err) return reject(err);
resolve(data);
});
}
}
User.findOneAsync = function(queryObj) {
return new Promise(function(resolve, reject) {
User.findOne(queryObj, function(err, data) {
if (err) return reject(err);
resolve(data);
});
}
}
router.get('/:id', function (req, res, next) {
List.findOneAsync({listurl: req.params.id}).then(function(doc) {
if (!doc) {
throw "Empty Document";
}
var promises = doc.comments.map(function(item, index) {
return User.findOneAsync({Name: item.commenter}).then(function(data) {
if (data) {
item.gvUrl = gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'});
} else {
item.gvUrl = 'noGravs';
}
});
});
return Promise.all(promises).then(function() {
return doc;
});
}).then(function(doc) {
res.render('list', {
appTitle: doc.Title,
list: doc
});
}).catch(function(err) {
res.status(404).render('404', {appTitle: "Book not found"});
});
}
console.log (док) и убедитесь, что это не определено себя. но это до var z = 0; – Matt
Голосование для повторного открытия, потому что, хотя дуб объясняет, почему результат «неопределен», он не объяснил, как наилучшим образом решить эту проблему со многими операциями в полете в одно и то же время. – jfriend00