如何呈现异步 forEach 'find' 函数数组的结果?
How to render result of an array of asynchronous forEach 'find' functions?
这是我的简单任务:按 id 数组查找图像并将图像值渲染到模板中。
router.get('/gallery', function(req, res) {
var images = [];
imagesIds.forEach(function(eachImageId) {
Images.findById(eachImageId).exec(function(findImageErr, foundImage) {
if (foundImage) {
images.push(foundImage);
}
});
});
res.render('gallery', {
images: images
});
});
问题是 'res.render' 函数没有等待 'findById' 函数完成。 'images' 数组总是变为“[]”为空。
我尝试使用生成器,但不知道如何实现。
如果有人能在没有图书馆的情况下解释(比如q)会更好。因为我想深入了解generator是如何处理这个问题的。
按照你的要求,没有第三方也可以做到,但是会很麻烦...
无论如何,最重要的是在回调函数 "function(findImageErr,foundImage){..}".
中进行
1) 没有第 3 方你 - 你只需要在所有图像都被考虑后渲染:
var images = [];
var results=0;
imagesIds.forEach(function(eachImageId) {
Images.findById(eachImageId).exec(function(findImageErr, foundImage) {
results++;
if(foundImage)
images.push(foundImage);
if(results == imagesIds.length)
res.render('gallery',{images:images});
});
});
2) 我强烈推荐第 3 方也这样做。
我目前正在使用 async,但将来可能会迁移到 promises。
async.map(
imageIds,
function(eachImageId,next){
Images.findById(eachImageId).exec(function(findImageErr, foundImage) {
next(null,foundImage);
// don't report errors to async, because it will abort
)
},
function(err, images){
images=_.compact(images); // remove null images, i'm using lodash
res.render('gallery',{images:images});
}
);
已编辑:根据您的可读性评论,请注意您是否为 'findById(...).exec(...)' 创建了一些忽略错误并仅将其报告为 null 的包装函数(称之为 'findIgnoreError'(imageId, callback)) 那么你可以这样写:
async.map(
imageIds,
findIgnoreError,
function(err, images){
images=_.compact(images); // remove null images, i'm using lodash
res.render('gallery',{images:images});
}
);
换句话说,如果 reader 开始考虑函数,它会变得更具可读性......它在每个 imageId 上显示 "go over those imageIds in parallel, run "findIgnoreError",最后一节说明要做什么累积的结果...
我不会查询 mongo(或任何数据库)N 次,而是使用 $in:
触发单个查询
Images.find({ _id : { $in : imagesIds}},function(err,images){
if(err) return next(err);
res.render('gallery',{images:images});
});
这也会减少 io 的数量,而且您不必编写额外的代码来处理 res.render
生成器允许编写类似同步的函数,因为它们可以停止执行并稍后恢复。
I guess you already read some articles like this and know how to define generator function and use them.
您的异步代码可以表示为带有魔法 yield
关键字的简单迭代器。生成器函数将 运行 并在此处停止,直到您使用方法 next()
.
恢复它
function* loadImages(imagesIds) {
var images = [], image;
for(imageId of imagesIds) {
image = yield loadSingleImage(imageId);
images.push(image);
}
return images;
}
因为有一个循环,函数将遍历每个 next()
的循环,直到所有 imagesIds
都被遍历。最后会执行 return 语句,你会得到 images
.
现在我们需要描述图像加载。我们的生成器函数需要知道当前图像何时加载,然后才能开始加载。所有现代 javascript 运行 时代(node.js 和最新的浏览器)都有本机 Promise
对象支持,我们将定义一个 return 承诺的函数,它将是如果找到图像,最终会用图像解决。
function loadSingleImage(imageId) {
return new Promise((resolve, reject) => {
Images.findById(imageId).exec((findImageErr, foundImage) => {
if (foundImage) {
resolve(foundImage)
} else {
reject();
}
});
});
}
我们有两个函数,一个用于单个图像加载,另一个用于将它们放在一起。现在我们需要一些调度程序来将控制权从一个函数传递到另一个函数。由于您不想使用库,我们必须自己实现一些帮助程序。
它是 spawn function 的缩小版,可以更简单和更好地理解,因为我们不需要处理错误,而只是忽略丢失的图像。
function spawn(generator) {
function continuer(value) {
var result = generator.next(value);
if(!result.done) {
return Promise.resolve(result.value).then(continuer);
} else {
return result.value;
}
}
return continuer();
}
此函数在 continuer
函数中执行生成器的递归调用,而 result.done
不正确。一旦得到,就意味着生成已经成功完成,我们可以return我们的价值。
最后,将所有内容放在一起,您将获得以下用于图库加载的代码。
router.get('/gallery', function(req, res) {
var imageGenerator = loadImages(imagesIds);
spawn(imageGenerator).then(function(images) {
res.render('gallery', {
images: images
});
});
});
现在 loadImages
函数中有一些伪同步代码。我希望它有助于理解发电机的工作原理。
Also note that all images will be loaded sequently, because we wait asynchronous result of loadSingleImage
call to put it in array, before we can go to the next imageId
. It can cause performance issues, if you are going to use this way in production.
相关链接:
这是我的简单任务:按 id 数组查找图像并将图像值渲染到模板中。
router.get('/gallery', function(req, res) {
var images = [];
imagesIds.forEach(function(eachImageId) {
Images.findById(eachImageId).exec(function(findImageErr, foundImage) {
if (foundImage) {
images.push(foundImage);
}
});
});
res.render('gallery', {
images: images
});
});
问题是 'res.render' 函数没有等待 'findById' 函数完成。 'images' 数组总是变为“[]”为空。
我尝试使用生成器,但不知道如何实现。
如果有人能在没有图书馆的情况下解释(比如q)会更好。因为我想深入了解generator是如何处理这个问题的。
按照你的要求,没有第三方也可以做到,但是会很麻烦... 无论如何,最重要的是在回调函数 "function(findImageErr,foundImage){..}".
中进行1) 没有第 3 方你 - 你只需要在所有图像都被考虑后渲染:
var images = [];
var results=0;
imagesIds.forEach(function(eachImageId) {
Images.findById(eachImageId).exec(function(findImageErr, foundImage) {
results++;
if(foundImage)
images.push(foundImage);
if(results == imagesIds.length)
res.render('gallery',{images:images});
});
});
2) 我强烈推荐第 3 方也这样做。 我目前正在使用 async,但将来可能会迁移到 promises。
async.map(
imageIds,
function(eachImageId,next){
Images.findById(eachImageId).exec(function(findImageErr, foundImage) {
next(null,foundImage);
// don't report errors to async, because it will abort
)
},
function(err, images){
images=_.compact(images); // remove null images, i'm using lodash
res.render('gallery',{images:images});
}
);
已编辑:根据您的可读性评论,请注意您是否为 'findById(...).exec(...)' 创建了一些忽略错误并仅将其报告为 null 的包装函数(称之为 'findIgnoreError'(imageId, callback)) 那么你可以这样写:
async.map(
imageIds,
findIgnoreError,
function(err, images){
images=_.compact(images); // remove null images, i'm using lodash
res.render('gallery',{images:images});
}
);
换句话说,如果 reader 开始考虑函数,它会变得更具可读性......它在每个 imageId 上显示 "go over those imageIds in parallel, run "findIgnoreError",最后一节说明要做什么累积的结果...
我不会查询 mongo(或任何数据库)N 次,而是使用 $in:
触发单个查询Images.find({ _id : { $in : imagesIds}},function(err,images){
if(err) return next(err);
res.render('gallery',{images:images});
});
这也会减少 io 的数量,而且您不必编写额外的代码来处理 res.render
生成器允许编写类似同步的函数,因为它们可以停止执行并稍后恢复。
I guess you already read some articles like this and know how to define generator function and use them.
您的异步代码可以表示为带有魔法 yield
关键字的简单迭代器。生成器函数将 运行 并在此处停止,直到您使用方法 next()
.
function* loadImages(imagesIds) {
var images = [], image;
for(imageId of imagesIds) {
image = yield loadSingleImage(imageId);
images.push(image);
}
return images;
}
因为有一个循环,函数将遍历每个 next()
的循环,直到所有 imagesIds
都被遍历。最后会执行 return 语句,你会得到 images
.
现在我们需要描述图像加载。我们的生成器函数需要知道当前图像何时加载,然后才能开始加载。所有现代 javascript 运行 时代(node.js 和最新的浏览器)都有本机 Promise
对象支持,我们将定义一个 return 承诺的函数,它将是如果找到图像,最终会用图像解决。
function loadSingleImage(imageId) {
return new Promise((resolve, reject) => {
Images.findById(imageId).exec((findImageErr, foundImage) => {
if (foundImage) {
resolve(foundImage)
} else {
reject();
}
});
});
}
我们有两个函数,一个用于单个图像加载,另一个用于将它们放在一起。现在我们需要一些调度程序来将控制权从一个函数传递到另一个函数。由于您不想使用库,我们必须自己实现一些帮助程序。
它是 spawn function 的缩小版,可以更简单和更好地理解,因为我们不需要处理错误,而只是忽略丢失的图像。
function spawn(generator) {
function continuer(value) {
var result = generator.next(value);
if(!result.done) {
return Promise.resolve(result.value).then(continuer);
} else {
return result.value;
}
}
return continuer();
}
此函数在 continuer
函数中执行生成器的递归调用,而 result.done
不正确。一旦得到,就意味着生成已经成功完成,我们可以return我们的价值。
最后,将所有内容放在一起,您将获得以下用于图库加载的代码。
router.get('/gallery', function(req, res) {
var imageGenerator = loadImages(imagesIds);
spawn(imageGenerator).then(function(images) {
res.render('gallery', {
images: images
});
});
});
现在 loadImages
函数中有一些伪同步代码。我希望它有助于理解发电机的工作原理。
Also note that all images will be loaded sequently, because we wait asynchronous result of
loadSingleImage
call to put it in array, before we can go to the nextimageId
. It can cause performance issues, if you are going to use this way in production.
相关链接: