如何渲染异步forEach'find'函数数组的结果?

时间:2015-09-07 03:50:39

标签: javascript node.js asynchronous generator ecmascript-6

这是我的一项简单任务:通过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)会更好。因为我想深入了解发电机如何处理这个问题。

3 个答案:

答案 0 :(得分:2)

生成器允许编写类似于同步的函数,因为它们可以停止执行并在以后恢复它。

  

我猜你已经读过一些像this这样的文章了解如何定义生成器函数并使用它们。

您的异步代码可以表示为带有魔术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对象支持,我们将定义一个返回promise的函数,如果找到它将最终用图像解析。

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不为真。一旦获得,这意味着生成已成功完成,我们可以返回我们的价值。

最后,将所有内容放在一起,您将获得以下用于库加载的代码。

router.get('/gallery', function(req, res) {
    var imageGenerator = loadImages(imagesIds);
    spawn(imageGenerator).then(function(images) {
        res.render('gallery', {
            images: images
        });
    });
});

现在loadImages函数中有一些伪同步代码。我希望了解生成器的工作原理有所帮助。

  

另请注意,所有图像都会被后续加载,因为我们等待loadSingleImage调用的异步结果将其放入数组中,然后我们才能转到下一个imageId。如果您打算在生产中使用这种方式,它可能会导致性能问题。

相关链接:

答案 1 :(得分:1)

如你所知,它可以在没有第三方的情况下完成,但这会很麻烦...... 无论如何,底线是在回调函数"函数(findImageErr,foundImage){...}"。

内完成。

1)如果没有第三方,您只需在考虑所有图像后进行渲染:

 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)我强烈建议第三方也这样做。 我目前正在使用异步,但我可能会在将来迁移到承诺。

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});
   }
);

换句话说,如果读者开始思考函数,它会变得更具可读性......它说"并行查看那些imageIds,运行" findIgnoreError"在每个imageId上,最后一节说明如何处理累积的结果......

答案 2 :(得分:0)

我没有N次查询mongo(或任何数据库),而是使用$ in激发单个查询:

Images.find({ _id : { $in : imagesIds}},function(err,images){
   if(err) return next(err);
   res.render('gallery',{images:images});
});

这也会减少io的数量,而且你不必编写其他代码来处理 res.render