此路由器用于访问特定列表。在示例中,comments
是一个对象数组。当我在gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'});
对象中插入此comments
时,它已成功完成。但在前端(我使用玉)。我尝试使用此list.comments[i].gvUrl
,它返回undefined。它甚至会在for循环中返回undefined!我做错了什么?
router.get('/:id', function (req, res, next) {
List.findOne({listurl: req.params.id}, function (err, doc) {
var z = 0;
if (!err && doc != null) {
for (var i = 0; i < doc.comments.length; i++) {
User.findOne({Name: doc.comments[i].commenter}, function (err, data) {
if (data) {
doc.comments[z++].gvUrl = gravatar.url(data.Email, {s: '200', r: 'pg', d: '404'});
} else {
doc.comments[z++].gvUrl = 'noGravs';
}
});
}
//this line returns unfefined;
console.log(doc.comments[0].gvUrl);
res.render('list', {
appTitle: doc.Title,
list: doc
});
}
else {
res.status(404).render('404', {appTitle: "Book not found"});
}
});
});
答案 0 :(得分:1)
您正在for
循环内启动一堆异步操作,并期望在for
循环完成后完成所有异步操作。但是,实际上,它们都没有完成,因此您的doc.comments
数组尚未填充。您在填充之前尝试使用它,这就是为什么您在尝试使用它时将其视为已定义的原因。
解决此问题的最佳方法是学习如何使用Promise,然后使用Blubird&#39; Promise.map()
或ES6 Promise.all()
等多个请求触发,然后让promise引擎告诉您何时所有请求都已完成。
如果没有将数据库调用转换为使用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()
异步操作。蓝鸟承诺实施
以下是如何使用Bluebird Promises库并将数据库操作转换为支持Promises的方法:
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
对象添加有效方法,以便为每个返回承诺的方法添加新版本。List.findOneAsync()
。方法名称上的"Async"
后缀表示.promisifyAll()
添加的新方法。doc
,那么将拒绝承诺的投掷将在最后的.catch()
处理。 Promise是异步throw-safe,非常方便异步错误处理。Promise.map()
上的doc.comments
。这将自动迭代doc.comments
数组并在数组中的每个项上调用迭代器(类似于Array.prototype.map()
,除了此处它收集迭代器返回的所有promise并返回一个新的promise,当所有解决了底层的promises。通过这种方式,它允许所有迭代器并行运行,并告诉你何时完成所有迭代器。User.findOneAsync()
并使用结果设置doc.comments[index].gvUrl
值。.then()
上有一个额外的Promise.map()
处理程序,只是为了将该承诺的已解析值更改为doc
对象,以便我们可以从外部获取承诺处理程序。ES6承诺实施
这可以通过没有Bluebird承诺库的直接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"});
});
}