async.waterfall 在 for 循环内转义 for 循环

async.waterfall inside a for loop escapes the for loop

POST 类型的 Form Action 上,我们获取 Node.JS/Express 中的所有值并尝试将其保存到 MongoDB.

隐藏字段确定来自前端 javascript 的 属性 的长度,并且它的值更新为隐藏字段的值。

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

我有一个 async.waterfall 函数和一个 for loop 运行ning 像这样。

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 然后返回 [= 的正确值18=] 超过 (itemLength) 个目的地。

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

我们不能使用 async.eachSeries 而不是 for Loop,因为我只是迭代数字 属性 而我们没有任何 documents to iterate over

运行 for Loopasync.waterfall 中的任何可行解决方案?

提前致谢。

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

尝试用这样的东西替换 callback(null, destinationsArray);

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

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

其实我更喜欢djskinner提出的方法。但是,由于出现 save() 错误时会发生 console.log(),回调的 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.

与其说是 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);
              }
            }
          });

您那里的代码几乎没有问题:

  1. 调用回调的位置。
  2. res.redirect() 接到电话。
  3. for 循环。

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