for循环中的async.waterfall转义for循环

时间:2015-04-24 07:59:40

标签: javascript node.js mongodb asynchronous mongoose

Form ActionPOST上,我们会抓取Node.JS/Express中的所有值并尝试将其保存到MongoDB

隐藏字段从前端javascript确定属性的长度,并将其值更新为隐藏字段的值。

此长度在后端(节点)中用于迭代项目列表。

我有一个async.waterfall函数和一个for loop在其中运行。

async.waterfall([
function(callback){
       var itemLength = req.body.itemLength;
        var itemProp,itemComponent;
        var destination;
        var destinationsArray =[];

        for(var k=1; k<=itemLength; k++){

            destination = new Destination({
                name: req.body['destinationName'+k],
            });

            itemComponent = {
              "itemCompProp" : req.body['itemCompProp'+k]
            };


            itemProp = new ItemProp({
               itemComponent: itemComponent
            });

            itemProp.save(function(err,itemPropSaved){
              destination.newProperty = itemPropSaved._id

              destination.save(function(err,destinationSaved){
                if(err){
                  console.log("Error== " + err);
                }
                else{
                  destinationsArray.push(destinationSaved._id);
                }
              });

            });
         }// End of For
  callback(null,destinationsArray);
},
function(destinationsArray,callback){
   var brand = new Brand({
    name : req.body.brandName,
  });

  brand.save(function(err,brandSaved){
      if(err){
          console.log("Error== " + err);
        }else{
            console.log('Brand Saved');
        }
   });
   callback(null);
}
], function (err, status) {
  if(err){
    req.flash('error', {
          msg: 'Error Saving Brands'
      });

     console.log("Error : " + err); 
  }  
  else{
      console.log("Brand Saved."); 
      req.flash('success', {
          msg: 'Brand Successfully Added!'
      });
  }
});

res.redirect('/redirectSomewhere');

运行时,destinationsArray首先返回null,而不是通过for loop然后返回destinationsArray的正确值{} (itemLength)个目的地。

我们希望这个过程是同步的。我们还尝试使用封装for Loop的封闭但无济于事。

我们无法使用async.eachSeries代替for Loop,因为我只是在数字属性上进行迭代而我们没有任何documents to iterate over

for Loop内运行async.waterfall的任何可行解决方案?

提前干杯谢谢。

3 个答案:

答案 0 :(得分:2)

您所拥有的代码几乎没有问题:

  1. 调用回调的地方。
  2. res.redirect()接到电话。
  3. for 循环。
  4. save()是异步的。常规 for 循环将继续,而不等待所有save()调用完成。这就是destinationsArray为空的原因。正如您所说,您不能使用async.eachSeries(),因为您正在迭代数字属性。但是,你在那里正确。 Async.whilst()就是这样做的。以下是使用Async.whilst()的修订代码以及回调的正确调用位置:

    async.waterfall([
      function(callback){
        var itemLength = req.body.itemLength;
        var itemProp,itemComponent;
        var destination;
        var destinationsArray =[];
        var k = 1;  // 1st part of for loop:  for(k=1; k<=itemLength; k++)
    
        async.whilst(
          function() {
            return k <= itemLength;  // 2nd part of for loop:  for(k=1; k<=itemLength; k++)
          },
          function(whilstCb) {
            destination = new Destination({
              name: req.body['destinationName'+k]
            });
    
            itemComponent = {
              "itemCompProp" : req.body['itemCompProp'+k]
            };
    
            itemProp = new ItemProp({
              itemComponent: itemComponent
            });
    
            itemProp.save(function(err,itemPropSaved){
              destination.newProperty = itemPropSaved._id
    
              destination.save(function(err,destinationSaved){
                if(err){
                  console.log("Error== " + err);
                } else {
                  destinationsArray.push(destinationSaved._id);
                }
                k++;  // 3rd part of for loop:  for(k=1; k<=itemLength; k++)
                whilstCb(null);
              });
            });
          },
          function(err) {
            // It gets here once the loop is done
            console.log(destinationsArray);  // This array should have all the values pushed
            callback(null, destinationsArray);
          }
        );
      },
      function(destinationsArray,callback){
        var brand = new Brand({
          name : req.body.brandName
        });
    
        brand.save(function(err,brandSaved){
          if(err){
            console.log("Error== " + err);
          } else {
            console.log('Brand Saved');
          }
          callback(null);
        });
      }
    ], function (err, status) {
      if(err){
        req.flash('error', {
          msg: 'Error Saving Brands'
        });
        console.log("Error : " + err);
      } else {
        console.log("Brand Saved.");
        req.flash('success', {
          msg: 'Brand Successfully Added!'
        });
      }
      res.redirect('/redirectSomewhere'); 
    });
    

答案 1 :(得分:1)

问题与callback(null, destinationsArray);for loop之外调用有关,而不先检查循环是否已完成。

尝试使用以下内容替换callback(null, destinationsArray);

if (itemLength > 0 && destinationsArray.length === k - 1)  {
    callback(null, destinationsArray);
} else {
    callback(true);
}

以上检查以确保destination.save()成功完成正确的次数。

我实际上更喜欢djskinner提出的方法。但是,由于console.log()出现save()错误,因此回调destinationsArray可能包含错误的项目数。要解决此问题,您可以确保使用console.log("Error== " + err);之类的内容替换callback(err)以结束返回错误的瀑布。此外,k === itemLength检查未正确考虑应保存的正确项目数。这应该替换为k === destinationsArray.length

我做了修改以解决此问题,并在下面发布了更新版本。

destination.save(function(err, destinationSaved){
    if (err) {
        callback(err);
    }
    else {
        destinationsArray.push(destinationSaved._id);
        if (k === destinationsArray.length) {
            callback(null, destinationsArray);
        }
    }
});

- 编辑 - 我非常喜欢Ben使用whilst()发布的解决方案。这允许创建循环,其中迭代连续运行。有关详细信息,请查看npm页面here

答案 2 :(得分:1)

它不是那么导致你出现问题的for循环,而是save是一个异步操作。 for循环完成,并且在任何save回调有机会完成之前执行回调。

您要做的是在执行所有目标保存回调后调用async.waterfall回调。类似的东西:

         destination.save(function(err,destinationSaved){
            if(err){
              console.log("Error== " + err);
            } else {
              destinationsArray.push(destinationSaved._id);
              if (k === itemLength) {
                  // all destination callbacks have completed successfully
                  callback(null, destinationsArray);
              }
            }
          });