简体   繁体   English

for循环内的async.waterfall逃脱了for循环

[英]async.waterfall inside a for loop escapes the for loop

On a Form Action of type POST , we fetch all the values in Node.JS/Express and try saving it into MongoDB . POST类型的Form Action上,我们获取Node.JS/Express所有值,然后尝试将其保存到MongoDB

A hidden field determines the length of a property from the frontend javascript and it's value is updated as the hidden field's value. 隐藏字段确定前端javascript中属性的长度,并且其值将更新为隐藏字段的值。

This length is used in the backend (Node) to iterate over a list of items. 在后端(节点)中使用此长度来迭代项目列表。

I have a async.waterfall function and a for loop running inside it like this. 我有一个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');

When we run this, The destinationsArray is returned first as null , as opposed to going through the for loop and then returning the proper value of destinationsArray over a length ( itemLength ) of destinations. 当我们运行此命令时, destinationsArray首先返回null ,而不是通过for loop ,然后在destinationsArray的长度( itemLength )上返回destinationsArray的正确值。

We want the process to be synchronous. 我们希望过程是同步的。 We also tried using a closure wrapping the for Loop but to no avail. 我们还尝试使用将for Loop包装起来的闭包for Loop但无济于事。

We can't use a async.eachSeries instead of the for Loop as I am just iterating over a numeric property and we don't have any documents to iterate over 我们不能使用async.eachSeries而不是for Loop因为我只是在数值属性上进行迭代,并且没有任何documents to iterate over

Any feasible solution to run a for Loop inside a async.waterfall ? async.waterfall内运行for Loop可行解决方案吗?

Cheers and Thanks in Advance. 预先加油和感谢。

There are few problems with the code you have there: 您那里的代码几乎没有问题:

  1. where the callbacks got called. 回调被调用的地方。
  2. where res.redirect() got call. res.redirect()在哪里被调用。
  3. the for loop. for循环。

save() is asynchronous. save()是异步的。 Regular for loop will just continue without waiting for all save() calls to finish. 常规的for循环将继续,而无需等待所有save()调用完成。 That's why destinationsArray is empty. 这就是为什么destinationsArray为空的原因。 As you said, you cannot use async.eachSeries() since you're iterating through numeric property. 如您所说,您不能使用async.eachSeries(),因为您要遍历数字属性。 However, you're on the right track there. 但是,您在那儿走对了。 Async.whilst() does just that. Async.whilst()就是这样做的。 Here is the revised code with Async.whilst() and proper calling locations of the callbacks: 这是带有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'); 
});

The issue has to do with callback(null, destinationsArray); 这个问题与callback(null, destinationsArray); getting called outside the for loop without checking first to see the loop has been finished. for loop外被调用而无需先检查循环是否完成。

Try replacing callback(null, destinationsArray); 尝试替换callback(null, destinationsArray); with something like this: 像这样:

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

The above checks to make sure the destination.save() gets completed the proper number of times successfully. 上面的检查可确保destination.save()成功完成正确的次数。

I actually prefer the method proposed by djskinner. 我实际上更喜欢djskinner提出的方法。 However, because of the console.log() that occurs when there is a save() error, the callbacked destinationsArray could possibly hold the incorrect number of items. 但是,由于在发生save()错误时会发生console.log() ,所以回调的destinationsArray可能容纳了错误数量的项目。 To fix this, you could make sure to replace the console.log("Error== " + err); 要解决此问题,您可以确保替换console.log("Error== " + err); with something like callback(err) to end the waterfall with the error returned. 用类似callback(err)类的东西来结束瀑布,并返回错误。 In addition, the k === itemLength check doesn't properly account for the correct number of items that should be saved. 此外, k === itemLength检查未正确说明应保存的正确项目数。 This should be replaced with k === destinationsArray.length . 应该用k === destinationsArray.length代替。

I made modifications to fix this and posted an updated version below. 我进行了修改以解决此问题,并在下面发布了更新版本。

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

--EDIT-- I really like the solution that Ben posted using whilst() . -编辑-我真的很喜欢Ben使用while whilst()发布的解决方案。 This allows the creation of a loop where the iterations runs serially. 这样就可以创建一个循环,在该循环中迭代将顺序运行。 For more info, view the npm page here . 有关更多信息,请在此处查看npm页面。

Its not so much the for loop that is causing you problems but that save is an asynchronous operation. 它并不是因为for循环引起您的问题,而是save是异步操作。 The for loop completes and the callback is executed before any of the save callbacks have had chance to complete. for循环完成,并且在任何save回调都有机会完成之前执行回调。

What you want to do is call the async.waterfall callback after all the destination save callbacks have been executed. 您要执行的操作是在执行所有目标保存回调之后,调用async.waterfall回调。 Something like: 就像是:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM