在下一个开始之前等待先前承诺的Javascript Map?

时间:2016-12-17 03:41:59

标签: javascript node.js promise ecmascript-6

我知道这不在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);
                    });
                });
            });
}

5 个答案:

答案 0 :(得分:10)

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

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




答案 1 :(得分:1)

如果您使用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) => {
  ..//
});

答案 2 :(得分:1)

你可以使用一种递归:

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

答案 3 :(得分:1)

您可以使用递归解决方案

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 :(得分:0)

我给了它一个想法,但我找不到比减少一个更好的方法。

根据你的情况改编它会是这样的:

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的一些解释。