使用 Promise.all() 在 Promise 实现时执行操作
Posted
技术标签:
【中文标题】使用 Promise.all() 在 Promise 实现时执行操作【英文标题】:Perform actions as promises get fulfilled using Promise.all() 【发布时间】:2017-02-05 07:54:12 【问题描述】:我可以用Promise.all(array)
异步解决一堆promise。然而.then()
只会在所有这些承诺都得到解决后运行。当承诺得到解决时,我如何执行操作?
例如,我想使用 Promise.all()
异步加载文章中的所有段落 - 这样网络请求会立即全部触发。如果第 1 段完成加载,我希望它呈现到页面 - 但只有在第 2 段之前完成加载,然后我希望第 2 段加载。如果第 3 段已完成加载而第 2 段未加载,我希望 3 在渲染到页面之前等待 2。以此类推。
我尝试了类似的方法,但我不知道下一步该做什么:
var getStuff = function(number, time)
return new Promise(function(resolve, reject)
window.setTimeout(function()resolve(`$number - Done.`), time);
);
;
Promise.all([ getStuff(1, 200),
getStuff(2, 100),
getStuff(3, 250),
getStuff(4, 200),
getStuff(5, 300),
getStuff(6, 250),
getStuff(7, 5000)])
.then(function(data)
console.log(data);
);
我怎样才能让数据的控制台日志一个接一个地发生 - 在发出下一个请求之前,不用then()
解决每个承诺?有没有更好的方法来做到这一点?
【问题讨论】:
一些 Promise 库有 progress 回调。 没有办法使用原生 es6 获得这种行为?不向我的项目添加另一个库? 为什么不为每个承诺只渲染getStuff(...)
和then
?
我不认为它是本地可用的。不过,您仍然可以拥有一系列承诺,并且每个承诺都有一个单独的 then()
。
@nem035 哦,我错过了那部分。
【参考方案1】:
您无法使用Promise.all
实现此顺序,因为从Promise.all
返回的promise 会等待所提供数组中的所有promise 在它自己解析之前同时(而不是顺序)解析。
相反,您可以单独创建它们的请求的承诺和触发:
// create promises and make concurrent requests
const s1 = getStuff(1, 200);
const s2 = getStuff(2, 100);
const s3 = getStuff(3, 250);
// ...
然后创建一个关于如何处理它们的反应链(stuff1 在 stuff2 之前,stuff2 在 stuff3 之前,等等)
// create a chain of reaction order to the results of parallel promises
s1
.then(console.log) // s1 resolved: log result
.then(() => s2) // chain s2
.then(console.log) // s2 resolved: log result
.then(() => s3) // chain s3
// ...
.then(() => // chain another function at at the end for when all promises resolved
// all promises resolved (all data was logged)
要按照创建 Promise 的顺序对 Promise 结果做出反应,您可以更改 getStuff
函数以使用 Array.prototype.reduce
动态链接反应:
var times = [200, 100, 250, 200, 300, 250, 5000];
var getStuff = function(time, index) // swap the order of arguments so number is the index passed in from Array.map
return new Promise((resolve, reject) =>
window.setTimeout(() =>
resolve(`$index + 1 - Done.`); // use index + 1 because indexes start at 0
, time);
);
;
times
// map each time to a promise (and number to the index of that time + 1) and fire of a request
.map(getStuff)
// dynamically build a reaction chain for the results of promises
.reduce((chain, promise) =>
return chain
.then(() => promise)
.then(console.log);
, Promise.resolve())
.then(() =>
// all promises resolved (all data was logged in order)
);
【讨论】:
啊,这很有道理!基本上,一旦我调用 getStuff,他们就会发出网络请求,如果在之后执行 then 链,我将得到我想要的行为,因为每个 promise 都将在链中调用 .then 之前解决? 一旦你创建了一个 Promise,它就会运行你的请求。在那之后,您可以在任何时候构建链,无论该承诺是否已解决。如果承诺在您创建链之前解决,它将保持其价值,直到您创建链。如果你先做链,链会等待 Promise 接收值。这就是诺言的美妙之处。它们总是异步解析,你总是可以假装在用它们编码时已经有了这些值。【参考方案2】:nem035 的回答很到位。我想指出,在这种情况下,您通常希望在请求发生时采取相同的操作,并在请求全部完成时采取另一个行动。
您可以使用.all
和.map
:
Promise.all([ getStuff(1, 200),
getStuff(2, 100),
getStuff(3, 250),
getStuff(4, 200),
getStuff(5, 300),
getStuff(6, 250),
getStuff(7, 5000)]
.map(request => request.then(v =>
console.log("Request done! Got," v); // or some action per request
return v;
)).then(data => console.log(data));
您可以通过 .map
进一步控制这一点,因为您对每个请求都使用相同的功能:
Promise.all([[1, 200],
[2, 100],
[3, 250],
[4, 200],
[5, 300],
[6, 250],
[7, 5000]])
.map((a, b) => getStuff(a, b))
.map(request => request.then(v =>
console.log("Request done! Got," v); // or some action per request
return v;
)).then(data => console.log(data));
还有:
Promise.all([200, 100, 250, 200, 300, 250, 5000])
.map((a, i) => getStuff(a, i + 1))
.map(request => request.then(v =>
console.log("Request done! Got," v); // or some action per request
return v;
)).then(data => console.log(data));
或与蓝鸟:
const sideEffect = v => console.log("Got partial result", v));
const data = [200, 100, 250, 200, 300, 250, 5000];
Promise.map(data, (a, i) => getStuff(a, i + 1).tap(sideEffect))
.then(data => console.log(data));
当然 - 你应该只修复你的后端,要求客户端对数据的不同部分发出 7 次请求是完全不合理的 - 让后端取值范围。
【讨论】:
【参考方案3】:我知道它不是原生的,但是对于 bluebird,您可以使用 Promise.some
(在实现 count
承诺后完成)或 Promise.mapSeries
(在系列中完成承诺)以某种方式实现您期望的流程。
Bluebird API
【讨论】:
【参考方案4】:正常操作:您可以安全使用Promise.all()
。 Promise 执行器将被并行触发,结果将按照您将 Promise 插入 Promise 数组的顺序返回。然后,您可以按照自己喜欢的方式对它们进行排序。例如在下面的 sn-p 中,我们有五个 Promise,每个 Promise 都会在 5 秒内随机解决。无论他们的解决时间如何,您都会在最新解决时得到结果;
var promises = [ new Promise( v => setTimeout(_ => v("1st paragraph text"),~~(Math.random()*5000))),
new Promise( v => setTimeout(_ => v("2nd paragraph text"),~~(Math.random()*5000))),
new Promise( v => setTimeout(_ => v("3rd paragraph text"),~~(Math.random()*5000))),
new Promise( v => setTimeout(_ => v("4th paragraph text"),~~(Math.random()*5000))),
new Promise( v => setTimeout(_ => v("5th paragraph text"),~~(Math.random()*5000))),
];
Promise.all(promises)
.then(result => console.log(result.reduce((p,c) => p + "\n" + c)));
你想要什么:但是你不想等到最后一个完成,而是想按顺序处理它们,尽快解决它们。那么Array.prototype.reduce()
是你这里最好的朋友。比如
var promises = [ new Promise( v => setTimeout(_ => v("1st paragraph text"),~~(Math.random()*5000))),
new Promise( v => setTimeout(_ => v("2nd paragraph text"),~~(Math.random()*5000))),
new Promise( v => setTimeout(_ => v("3rd paragraph text"),~~(Math.random()*5000))),
new Promise( v => setTimeout(_ => v("4th paragraph text"),~~(Math.random()*5000))),
new Promise( v => setTimeout(_ => v("5th paragraph text"),~~(Math.random()*5000)))
];
promises.reduce((p,c) => p.then(result => (console.log(result + "\n"),c)))
.then(result => (console.log(result + "\n")));
请多次运行代码以查看代码的行为方式。文本将在承诺解决后尽快更新,但前提是轮到它。因此,如果第 1 次在第 2 次之后解决,我们将看到第 1 次和第 2 次按顺序同时出现,但他们不会等待第 3 次解决以此类推...
【讨论】:
以上是关于使用 Promise.all() 在 Promise 实现时执行操作的主要内容,如果未能解决你的问题,请参考以下文章
使用 Promise.all() 在 Promise 实现时执行操作
理解Promise.all,Promise.all与Promise.race的区别,如何让Promise.all在rejected后依然返回resolved状态