在开始下一个之前等待上一个承诺的Javascript Map?
Posted
技术标签:
【中文标题】在开始下一个之前等待上一个承诺的Javascript Map?【英文标题】:Javascript Map that waits for previous promise before starting next? 【发布时间】: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同样的事情。
相关:Promise version of a “while” loop?
SO 文档中也有Reduce an array to chained promises。
Bluebird's Promise.mapSeries()
会这样做。
【参考方案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的解释。
【讨论】:
以上是关于在开始下一个之前等待上一个承诺的Javascript Map?的主要内容,如果未能解决你的问题,请参考以下文章
使 angular.forEach 在转到下一个对象后等待承诺
在 Chrome 扩展程序中,如何确保在使用 chrome-promise 的下一个承诺之前解决上一个承诺?