【问题标题】:Javascript Map that waits for previous promise before starting next?在开始下一个之前等待上一个承诺的Javascript Map?
【发布时间】:2017-05-02 21:16:05
【问题描述】:

我知道这不在Array.map 的范围内,但我想等到前一项完成承诺后再开始下一项。碰巧我需要等待上一个条目保存在数据库中才能继续前进。

const statsPromise = stats.map((item) => {
    return playersApi.getOrAddPlayer(item, clubInfo, year); //I need these to wait until previous has finished its promise.
});

Promise.all(statsPromise)
.then((teamData) => {
  ..//
});

playersApi.getOrAddPlayer 返回一个new Promise

编辑

阅读更多内容,显示 playerApi.getOrAddPlayer 似乎很重要

getOrAddPlayer: function (item, clubInfo, year) {
    return new Promise((resolve, reject) => {

        var playerName = item.name.split(' '),
            fname = playerName[0].caps(),
            sname = playerName[1].caps();

                Players.find({
                    fname: fname,
                    sname: sname,
                }).exec()
                .then(function(playerDetails, err){
                    if(err) reject(err);
                    var savePlayer = new Players();
                    //stuff
                    savePlayer.save()
                    .then(function(data, err){
                        if(err)  reject(err);
                        item._id = data._id;
                        resolve(item);
                    });
                });
            });
}

【问题讨论】:

  • @FelixKling 我重新打开了这个,因为我觉得你提到的 dup 中的答案不是很好。我知道还有其他更好的副本。如果你能找到他们,请告诉我。
  • 你会原谅我的 cmets 的不可知论,但我并没有真正使用承诺(我知道我应该,但考虑到我不专业地这样做有不是理由),但我可能会考虑做的是将一系列需要完成的项目保留在 promise 函数的范围之外,然后只需 Array.pop()-ing 关闭数据并根据会做的事情创建一个新的 promise同样的事情。

标签: javascript node.js promise ecmascript-6


【解决方案1】:

您可以使用归约而不是映射来实现此目的:

stats.reduce(
  (chain, item) =>
    // append the promise creating function to the chain
    chain.then(() => playersApi.getOrAddPlayer(item, clubInfo, year)),
  // start the promise chain from a resolved promise
  Promise.resolve()
).then(() => 
  // all finished, one after the other
);

演示:

const timeoutPromise = x => {
  console.log(`starting ${x}`);
  return new Promise(resolve => setTimeout(() => {
    console.log(`resolving ${x}`);
    resolve(x);
  }, Math.random() * 2000));
};

[1, 2, 3].reduce(
  (chain, item) => chain.then(() => timeoutPromise(item)),
  Promise.resolve()
).then(() =>
  console.log('all finished, one after the other')
);

如果需要累加值,可以通过归约传播结果:

stats
  .reduce(
    (chain, item) =>
      // append the promise creating function to the chain
      chain.then(results =>
        playersApi.getOrAddPlayer(item, clubInfo, year).then(data =>
          // concat each result from the api call into an array
          results.concat(data)
        )
      ),
    // start the promise chain from a resolved promise and results array
    Promise.resolve([])
  )
  .then(results => {
    // all finished, one after the other
    // results array contains the resolved value from each promise
  });

演示:

const timeoutPromise = x => {
  console.log(`starting ${x}`);
  return new Promise(resolve =>
    setTimeout(() => {
      console.log(`resolving result for ${x}`);
      resolve(`result for ${x}`);
    }, Math.random() * 2000)
  );
};

function getStuffInOrder(initialStuff) {
  return initialStuff
    .reduce(
      (chain, item) =>
        chain.then(results =>
          timeoutPromise(item).then(data => results.concat(data))
        ),
      Promise.resolve([])
    )
}

getStuffInOrder([1, 2, 3]).then(console.log);

变体 #1:Array.prototype.concat 看起来更优雅,但会在每个连接上创建一个新数组。为了提高效率,您可以使用 Array.prototype.push 和更多样板:

stats
  .reduce(
    (chain, item) =>
      chain.then(results =>
        playersApi.getOrAddPlayer(item, clubInfo, year).then(data => {
          // push each result from the api call into an array and return the array
          results.push(data);
          return results;
        })
      ),
    Promise.resolve([])
  )
  .then(results => {

  });

演示:

const timeoutPromise = x => {
  console.log(`starting ${x}`);
  return new Promise(resolve =>
    setTimeout(() => {
      console.log(`resolving result for ${x}`);
      resolve(`result for ${x}`);
    }, Math.random() * 2000)
  );
};

function getStuffInOrder(initialStuff) {
  return initialStuff
    .reduce(
      (chain, item) =>
        chain.then(results =>
          timeoutPromise(item).then(data => {
            results.push(data);
            return results;
          })
        ),
      Promise.resolve([])
    );
}

getStuffInOrder([1, 2, 3]).then(console.log);

变体 #2:您可以将 results 变量提升到上限范围。这将消除嵌套函数的需要,以便在累积数据时通过最近的闭包使results 可用,而是使其对整个链全局可用。

const results = [];
stats
  .reduce(
    (chain, item) =>
      chain
        .then(() => playersApi.getOrAddPlayer(item, clubInfo, year))
        .then(data => {
          // push each result from the api call into the globally available results array
          results.push(data);
        }),
    Promise.resolve()
  )
  .then(() => {
    // use results here
  });

演示:

const timeoutPromise = x => {
  console.log(`starting ${x}`);
  return new Promise(resolve =>
    setTimeout(() => {
      console.log(`resolving result for ${x}`);
      resolve(`result for ${x}`);
    }, Math.random() * 2000)
  );
};

function getStuffInOrder(initialStuff) {
  const results = [];
  return initialStuff.reduce(
    (chain, item) =>
      chain
        .then(() => timeoutPromise(item))
        .then(data => {
          results.push(data);
          return results;
        }),
    Promise.resolve()
  );
}

getStuffInOrder([1, 2, 3]).then(console.log);

【讨论】:

  • 好朋友 :) 我必须说,我没有得到 `year)), // 从已解决的承诺开始承诺链 Promise.resolve()` 逗号在这里做?我们在每个循环中返回承诺链。我只是不明白该功能的设置方式,
  • 同样在这个例子中,我们没有将 playerApi 返回的值传递给 then
  • 逗号分隔传递给stats.reduce 的两个参数。第一个参数是一个函数,它接受 Promise 链和当前项并进一步构建链,第二个参数是一个空的已解析 Promise,从它开始链。
  • 就累积值而言,有两种方法。一种是在外部范围内创建一个数组,并在每次 Promise 解析时推送结果,(chain, item) => chain.then(() => playersApi.getOrAddPlayer(item, clubInfo, year)).then((result) => outerArray.push(result));。另一种是通过递归传播结果,如guest271314's answer 所示,或者通过每次调用reduce 或每次调用playersApi.getOrAddPlayer 传递结果。我将添加一个示例。
  • 我喜欢这种方法……太聪明了!谢谢!
【解决方案2】:

如果您对使用 Promise 库感到满意,您可以在这种情况下使用 Promise.mapSeries by Bluebird。

例子:

const Promise = require("bluebird");
//iterate over the array serially, in-order
Promise.mapSeries(stats, (item) => {
  return playersApi.getOrAddPlayer(item, clubInfo, year));
}).then((teamData) => {
  ..//
});

【讨论】:

    【解决方案3】:

    您可以使用递归解决方案

    const statsPromise = (function s(p, results) {
      return p.length ? playersApi.getOrAddPlayer(p.shift(), clubInfo, year) : results;
    })(stats.slice(0), []);
    
    statsPromise
    .then((teamData) => {
    //do stuff
    });
    

    let n = 0;
    let promise = () => new Promise(resolve => 
                    setTimeout(resolve.bind(null, n++), 1000 * 1 + Math.random()));
    
    let stats = [promise, promise, promise];
    
    const statsPromise = (function s(p, results) {
      return p.length ? p.shift().call().then(result => {
        console.log(result);
        return s(p, [...results, result])
      }) : results;
    })(stats.slice(0), []);
        
    statsPromise.then(res => console.log(res))

    【讨论】:

      【解决方案4】:

      你可以使用一种递归:

      function doStats([head, ...tail]) {
        return !head ? Promise.resolve() :
          playersApi.getOrAddPlayer(head, clubInfo, year)
            .then(() => doStats(tail));
      }
      
      doStats(stats)
        .then(() => console.log("all done"), e => console.log("something failed", e));
      

      另一个经典的方法是使用reduce:

      function doStats(items) {
        return items.reduce(
          (promise, item) => 
            promise.then(() => playersApi.getOrAddPlayer(item, clubInfo, year)),
          Promise.resolve());
      

      顺便说一句,你可以清理你的 getOrAddPlayer 函数,并避免使用 Promise 构造函数反模式:

      getOrAddPlayer: function (item, clubInfo, year) {
          var playerName = item.name.split(' '),
              fname = playerName[0].caps(),
              sname = playerName[1].caps();
      
          return Players.find({fname, sname}).exec()
            .then(playerDetails => new Players().save())
            .then({_id} => Object.assign(item, {_id}));
      }
      

      【讨论】:

      • Hot dam,这是一些干净的 JS。我对整个 Promise 链都很陌生 :) 对您的第一个函数很感兴趣,您可以使用 Promise.resolve() 作为完成时间的标记。
      • 在清理GetOrAddPlayer我相信你必须回报一个承诺。我没有意识到你可以从 Mongoose 中返回整个承诺链?
      • @JamieHutber 没错。你已经有了从猫鼬那里得到的承诺;你不需要将它包装在另一个承诺中。那是explicit promise constructor anti-pattern。
      • 对您的第一个函数很感兴趣,您可以使用Promise.resolve() 作为完成时间的标记。 对。这对应于如果你给它喂零个项目,你就成功了。
      • 我从来没有想过要像这样使用Promise.resolve。我一直把它想象成一种方式.,.. 好吧实际上是的,但我没有像这样应用它:D 现在我只需要返回在playersApi.getOrAddPlayer 中收集的数据,然后我们就可以了 :)
      【解决方案5】:

      我考虑了一下,但没有找到比 reduce 更好的方法。

      适应你的情况,它会是这样的:

      const players = [];
      const lastPromise = stats.reduce((promise, item) => {
        return promise.then(playerInfo => {
          // first iteration will be undefined
          if (playerInfo) {
             players.push(playerInfo)
          }
          return playersApi.getOrAddPlayer(item,  clubInfo, year);
        });
      }, Promise.resolve());
      
      // assigned last promise to a variable in order to make it easier to understand
      lastPromise.then(lastPlayer => players.push(lastPlayer));
      

      你可以看到一些关于这个here的解释。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-01-24
        • 1970-01-01
        • 2021-09-08
        • 1970-01-01
        • 2016-02-01
        • 1970-01-01
        相关资源
        最近更新 更多