使用 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.all() 在 Promise 实现时执行操作

vue promise.all使用

理解Promise.all,Promise.all与Promise.race的区别,如何让Promise.all在rejected后依然返回resolved状态

await 与 Promise.all 结合使用

await 与 Promise.all 结合使用