【问题标题】:Issue with promises in a for loopfor循环中的promise问题
【发布时间】:2016-08-09 21:00:00
【问题描述】:

我遇到了一个让我有点发疯的情况。

所以情况如下:

module.exports = {
  
  generation: function (req, res) {

    // Let's firstly fetch all the products from the productTmp Table
    function fetchProductsTmp (){
      ProductsTmp.find().then(function (products) {
        return Promise.all(products.map (function (row){
           Service.importProcess(row);
        }));
      });
    }
    fetchProductsTmp();
  }

在这里我只是调用我的模型 ProductsTmp 来获取我的数据并遍历我调用 importProcess 的行。

导入过程:

importProcess: function (product) {

    async.series([
      function (callback) {
        return SousFamille.findOne({name: product.sous_famille}).then(function (sf) {
          console.log('1');
          if (!sf) {
            return SousFamille.create({name: product.sous_famille}).then(function (_sf)             {
              console.log('2');
              callback(null, _sf.sf_id);
            });
          } else {
              callback(null, sf.sf_id);
          }
        });
      },
      function (callback){
        console.log('3');
      },
    ], function(err, results){
      if(err) return res.send({message: "Error"});

    });
      
}

所以我得到了我的控制台日志: 1 1 1 2 3 2 3 2 3

我想要获取的是 1 2 3 1 2 3 1 2 3 以便每个函数在调用下一个函数之前等待 promise 完成。

【问题讨论】:

  • 您忘记返回 Promise.all 的实际承诺。 return Promise.all(products.map (function (row){ return Service.importProcess(row); }));
  • 你真的不应该使用async.series,因为你可以只使用promise。
  • @noppa 感谢您的回答,但这并不能解决问题
  • @Bergi 为什么不推荐?
  • @msecz:因为 Promise 比 async 更好、更简单。在您的情况下,不返回来自 importProcess 的承诺甚至是您的代码不起作用的原因。

标签: javascript for-loop asynchronous callback promise


【解决方案1】:

在第一节的generation函数中,替换

return Promise.all(products.map (function (row){
    Service.importProcess(row);
}));

与

var results = [],
    pushResult = id => results.push(id);
return products.reduce(function(prev, row){//Go through all the products
    //Take the previous promise, and schedule next call to Service.importProcess to be
    //made after the previous promise has been resolved
    return prev.then(function(){
        return Service.importProcess(row).then(pushResult);
    });
}, Promise.resolve())
.then(() => results);

您还需要从importProcess 返回一个promise 才能使其正常工作。扔掉整个async.series 的东西,做类似的事情

return new Promise(function(resolve, reject){
     ...
     resolve(sf.sf_id); //instead of the callback(null, sf.sf_id)
     ...
});

更新:这会强制对 Service.importProcess 的调用是顺序的,而不是并发的,这确实会影响对 generation 的调用的整体性能。但我想你有比顺序 console.logs 更充分的理由这样做。

【讨论】:

  • 你错过了一件事,他可能需要所有的 id,在你当前的解决方案中,除了最后一个之外,它们可能会丢失......
  • 是的,我将不得不连续执行许多操作并保存 ID 以供以后使用。这就是为什么我首先使用异步来解决这个问题
  • @mido 你是对的。我已经更新了答案以涵盖该问题
【解决方案2】:

对不起,在 ES6 中控制不住做它的冲动,基本上事情可以简化为单行,就像 Bergi 说的,异步是多余的(使用 Bluebird Promise 库):

importProcess: product => 
                  SousFamille.findOne({name: product.sous_famille})
                    .then(sf =>  sf? sf.sf_id : SousFamille.create({name: product.sous_famille}).then(_sf => _sf.sf_id))

// the other module
module.exports = {

  generation: (req, res) => ProductsTmp.find()
                              .then(products => Promise.mapSeries(products, Service.importProcess.bind(Service)) )
                              .then(ids => res.send({ids}))
                              .catch(error => res.send({message: 'Error'}))
  }

也像 noppa 所说,你的问题是 Service.importProcess(row) 中缺少 return,ES5 中的代码相同:

module.exports = {

  generation: function (req, res) {

      ProductsTmp.find()
        .then(function (products) {
          return Promise.mapSeries(products, Service.importProcess.bind(Service)) );
      }).then(function(ids){
        res.send({ids: ids});
      }).catch(function(error){
        res.send({message: 'Error'});
      })
}


importProcess: function (product) {

    return SousFamille.findOne({name: product.sous_famille})
      .then(function (sf) {
          if (sf) return sf.sf_id;
          return SousFamille.create({name: product.sous_famille})
                    .then(function (_sf){ return _sf.sf_id});
    });      
}

【讨论】:

  • 这个importProcess 函数显然要好得多,但这并不能解决OP 的问题,即并发调用Service.importProcess。请参阅我的答案以解决此问题。
  • 我非常喜欢 ES6 的编写方式!但是我怎么能把我的动作串联起来,例如: SousFamille.findOne({name: product.sous_famille}).then.... Famille.findOne({})....有没有比列出长长的 then 更好的方法?
  • @msecz 你在用bluebird promises吗,如果是,你可以试试,Promise.mapSeries(products, Service.importProcess.bind(Service))而不是Promise.all...
  • @mido 不,我不推荐使用它吗?
  • @msecz 是的,非常有用的库,尤其是像 promisify 和 promisifyAll 这样将异步调用转换为 Promise 的方法,还有很多其他有用的方法...查看他们的 doc
猜你喜欢
  • 1970-01-01
  • 2023-04-06
  • 2022-10-16
  • 1970-01-01
  • 2019-08-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多