Promise.all vs [await x, await y] - 真的一样吗?

Promise.all vs [await x, await y] - Is it really the same?

这是一个基本问题,但我在任何地方都找不到答案。

我们有两种方法:

// consider someFunction1() and someFunction2() as functions that returns Promises

Approach #1:
return [await someFunction1(), await someFunction2()]

Approach #2:
return await Promise.all([someFunction1(), someFunction2()])

我的团队负责人说这两种方法最终都在同一个解决方案中(两个功能并行执行)。但是,据我所知,第一种方法是 await someFunction1() 解析然后执行 someFunction2.

那么问题来了,是不是真的一样,或者第二种方法有什么性能改进吗?非常欢迎提供证明!

很容易看出区别

    function createTimer(ms, id) {
      console.log(`id: ${id} started ${new Date()}`);
      return new Promise((res, rej) => {
        setTimeout( () => {
          console.log(`id: ${id} finished ${new Date()}`);
          res(id);      
        }, ms );
      });
    }

    (async function() {

      var result1 = [await createTimer(5000, '1'), await createTimer(5000, '2')];
      var result2 = await Promise.all([createTimer(5000, '3'), createTimer(5000, '4')]);

      console.log(result1);
      console.log(result2);

    })();

第一个开始 11 结束后开始 2。 第二个几乎在同一时刻开始 34

My Team Leader said that both approaches ended up in the same solution (both functions executting in parallel).

这是不正确的。

But, from my knowledge, the first approach would await someFunction1() to resolve and then would execute someFunction2.

没错。

这里有演示

方法一:

const delay = (ms, value) =>
  new Promise(resolve => setTimeout(resolve, ms, value));
  
async function Approach1() {
  return [await someFunction1(), await someFunction2()];
}
  
async function someFunction1() {
  const result = await delay(800, "hello");
  console.log(result);
  return result;
}

async function someFunction2() {
  const result = await delay(400, "world");
  console.log(result);
  return result;
}

async function main() {
  const start = new Date();
  const result = await Approach1();
  const totalTime = new Date() - start;
  console.log(`result: ${result}
    total time: ${totalTime}`);
}

main();

结果是:

hello
world
result: hello,world
    total time: 1205

表示someFunction1运行完成然后someFunction2执行。它是顺序的

方法二:

const delay = (ms, value) =>
  new Promise(resolve => setTimeout(resolve, ms, value));
  
async function Approach2() {
  return await Promise.all([someFunction1(), someFunction2()]);
}
  
async function someFunction1() {
  const result = await delay(800, "hello");
  console.log(result);
  return result;
}

async function someFunction2() {
  const result = await delay(400, "world");
  console.log(result);
  return result;
}

async function main() {
  const start = new Date();
  const result = await Approach2();
  const totalTime = new Date() - start;
  console.log(`result: ${result}
    total time: ${totalTime}`);
}

main();

结果是:

world
hello
result: hello,world
    total time: 803

这意味着 someFunction2someFunction2 之前 完成。两者平行

MDN documentation 对于 Promise.all() 表示

This method can be useful for aggregating the results of multiple promises. It is typically used when there are multiple related asynchronous tasks that the overall code relies on to work successfully — all of whom we want to fulfill before the code execution continues.

虽然不明确,但您可以等待 Promise.all 来跟踪多个承诺。只有当所有的承诺都被解决后,代码才会继续执行。

由于 await 的操作方式,您提到的在数组中捕获单独的异步任务的另一种方法并不相同。

An await splits execution flow, allowing the caller of the async function to resume execution. After the await defers the continuation of the async function, execution of subsequent statements ensues. If this await is the last expression executed by its function, execution continues by returning to the function's caller a pending Promise for completion of the await's function and resuming execution of that caller.

因此,每个 await 都会在恢复之前暂停执行。无需演示。

如果你从一个模拟做一些工作的函数开始,它在工作的各个阶段输出但需要一些时间。例如

function someFunction1(){
   return new Promise(resolve => {
       let i = 0;
       const intervalId = setInterval(() => {
          i++;
          console.log("someFunction1", i);
          if(i == 5){
            clearInterval(intervalId)
            resolve(1); 
          }
       }, 1000);
   });
}

然后你用第二种类似的方法复制它。您插入 2 个方法,您会看到使用 Promise.all 的方法并行执行,而使用 2 await 调用的方法串行执行。

平行

function someFunction1(){
   return new Promise(resolve => {
       let i = 0;
       const intervalId = setInterval(() => {
          i++;
          console.log("someFunction1", i);
          if(i == 5){
            clearInterval(intervalId)
            resolve(1); 
          }
       }, 1000);
   });
}

function someFunction2(){
   return new Promise(resolve => {
       let i = 0;
       const intervalId = setInterval(() => {
          i++;
          console.log("someFunction2", i);
          if(i == 5){
            clearInterval(intervalId)
            resolve(2); 
          }
       }, 1000);
   });
}

(async function(){
   const result = await Promise.all([someFunction1(),someFunction2()]);
   console.log("result",result);
})();

系列

function someFunction1(){
   return new Promise(resolve => {
       let i = 0;
       const intervalId = setInterval(() => {
          i++;
          console.log("someFunction1", i);
          if(i == 5){
            clearInterval(intervalId)
            resolve(1); 
          }
       }, 1000);
   });
}

function someFunction2(){
   return new Promise(resolve => {
       let i = 0;
       const intervalId = setInterval(() => {
          i++;
          console.log("someFunction2", i);
          if(i == 5){
            clearInterval(intervalId)
            resolve(2); 
          }
       }, 1000);
   });
}

(async function(){
   const result = [await someFunction1(),await someFunction2()];
   console.log("result",result);
})();

两者给出完全相同的结果,但实现方式却大不相同。

不,你不应该接受:

return [await someFunction1(), await someFunction2()];

等同于:

return await Promise.all([someFunction1(), someFunction2()]);

还要注意上面return await中的await是不需要的。查看 this blog post 以了解更多信息。

它们不同


第一种方法(顺序)

让我们通过检查两个备选方案的工作原理来确定差异。

[await someFunction1(), await someFunction2()];

在这里,在 async 上下文中,我们创建了一个数组文字。请注意 someFunction1 被调用(这个函数可能 returns 每次被调用时都是一个新的承诺)。

因此,当您调用 someFunction1 时,会返回一个新的承诺,然后它会“锁定”async 上下文,因为前面的 await.

简而言之,await someFunction1()“阻止”数组初始化,直到返回的承诺得到解决(通过解决或拒绝)。

someFunction2 重复相同的过程。

请注意,在第一种方法中,两个承诺按顺序等待。因此,与使用 Promise.all 的方法没有相似之处。让我们看看为什么。

第二种方法(非顺序

Promise.all([someFunction1(), someFunction2()])

当您应用 Promise.all 时,它需要一个可迭代的承诺。它会等待 所有 您给出的承诺在 returns 一个新的已解决值数组之前解决,但不要等待每个承诺解决直到等待另一个承诺。本质上是同时等待所有的promise,所以是一种“非顺序”。由于JavaScript是单线程的,你不能说这是“并行”,但从行为的角度来看是非常相似的。

所以,当你传递这个数组时:

[someFunction1(), someFunction2()]

您实际上传递了一组承诺(从函数返回)。类似于:

[Promise<...>, Promise<...>]

请注意,承诺正在外部 Promise.all.

所以你实际上是在将一组承诺传递给 Promise.all。当它们都被解析时,Promise.all returns 解析值数组。我不会详细解释 Promise.all 是如何工作的,为此,我建议您查看 documentation.

您可以通过使用 await 之前 创建承诺来复制这种“非顺序”方法。像这样:

const promise1 = someFunction1();
const promise2 = someFunction2();
return [await promise1, await promise2];

在等待 promise1 时,promise2 已经是 运行(因为它是在第一个 await 之前创建的),所以行为类似于 Promise.all的。