你正在发起了一堆异步操作的for
循环内,并且预计在for
循环完成做所有的异步操作。但是,实际上,他们中没有一个尚未完成,因此您的doc.comments
阵列尚未填充。您正在尝试在填充之前使用它,这就是为什么您会在您尝试使用它的地方看到它。
解决此问题的最佳方法是学习如何使用Promise,然后使用Blubird的Promise.map()
或ES6 Promise.all()
等类似方法触发多个请求,然后让promise引擎告诉您何时完成所有请求。
转换数据库的短促的叫声在使用的承诺,你可以手工编写知道如下当一切都做:
手工编码的回调实现
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在一个循环中。
- 这些异步操作可以按任意顺序完成。
- 循环完成后,这些异步操作将不会完成。所有的循环已经完成了启动操作。他们将在稍后完成一些不确定的时间。
- 要知道何时完成所有异步操作,它会保留一个计数器,以计数当计数达到启动的总请求数时已完成并呈现页面的数量。这就是“手动”方式,让您知道何时完成所有工作。
蓝鸟承诺实施
下面是它如何使用蓝鸟少辉库和转换你的数据库操作以支持承诺工作:
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"});
});
}
这里是如何工作的:
- 加载蓝鸟承诺库
- 向
User
和List
对象添加promisified方法,以便为每个返回承诺的方法添加新版本。
- 致电
List.findOneAsync()
。方法名称上的"Async"
后缀表示.promisifyAll()
添加的新方法。
- 如果没有
doc
,那么抛弃哪个将拒绝承诺,最后将在.catch()
中处理。 Promise是异步抛出安全的,对于异步错误处理非常方便。
- 致电
Promise.map()
在doc.comments
。这将自动迭代doc.comments
数组并在数组中的每个项目上调用一个迭代器(类似于Array.prototype.map()
,不同之处在于它收集了迭代器返回的所有promise,并返回一个新的promise,在所有基础promise被解析时解析。方式,它允许所有的迭代器并行运行,并告诉你,当所有的迭代完成。
- 迭代器调用
User.findOneAsync()
并设置doc.comments[index].gvUrl
值与结果。
- 有一个在
Promise.map()
只是一个额外的.then()
处理器将该承诺的解决价值更改为doc
对象,以便我们可以从外部承诺处理程序处获取该承诺
- 对于来自外部承诺的成功,渲染。
- 对于来自外部承诺的错误,请显示404页面。请记住,在整个计划的任何地方,任何被拒绝的承诺都会传播并成为最高层的拒绝。承诺中异步错误的自动传播非常有用。
ES6承诺实施
这可能与直ES6承诺完成,无需蓝鸟承诺库,但你必须用手工做一些事情:
- 您必须提醒
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(DOC),并确保它不是不确定的本身。但是这在var z = 0之前; – Matt
投票重新开放,因为尽管dup解释了为什么结果是“未定义”,但它没有解释如何在同一时间在飞行中进行许多操作以最好地解决这个特定问题。 – jfriend00