并行调用 async/await 函数

IT技术 javascript node.js asynchronous ecmascript-6 babeljs
2021-01-11 04:46:59

据我了解,在 ES7/ES2016 中,await在代码中放置多个's 将类似于.then()使用 promise链接,这意味着它们将一个接一个地执行,而不是并行执行。因此,例如,我们有以下代码:

await someCall();
await anotherCall();

我是否正确理解它anotherCall()只有在someCall()完成时才会被调用并行调用它们的最优雅方式是什么?

我想在 Node 中使用它,所以也许有一个带有异步库的解决方案?

编辑:我对这个问题中提供的解决方案不满意:Slowdown due to non-parallel awaits in async generators,因为它使用生成器,我正在询问更一般的用例。

6个回答

您可以等待Promise.all()

await Promise.all([someCall(), anotherCall()]);

存储结果:

let [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);

请注意,Promise.all失败很快,这意味着一旦提供给它的Promise之一被拒绝,整个事情就会被拒绝。

const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))

Promise.all([happy('happy', 100), sad('sad', 50)])
  .then(console.log).catch(console.log) // 'sad'

相反,如果您想等待所有Promise履行或拒绝,那么您可以使用Promise.allSettled. 请注意,Internet Explorer 本身并不支持此方法。

const happy = (v, ms) => new Promise((resolve) => setTimeout(() => resolve(v), ms))
const sad = (v, ms) => new Promise((_, reject) => setTimeout(() => reject(v), ms))

Promise.allSettled([happy('happy', 100), sad('sad', 50)])
  .then(console.log) // [{ "status":"fulfilled", "value":"happy" }, { "status":"rejected", "reason":"sad" }]

注意:如果您使用Promise.all在拒绝发生之前设法完成的操作不会回滚,因此您可能需要注意这种情况。例如,如果您有 5 个动作,4 个快速、1 个慢速和慢速拒绝。这 4 个操作可能已经执行,因此您可能需要回滚。在这种情况下,请考虑使用Promise.allSettledwhile 它将提供哪些操作失败以及哪些操作失败的确切详细信息。

@jonny 这是否会快速失败?另外,还需要= await Promise.all吗?
2021-03-21 04:46:59
@theUtherSide 你说得对 - 我忽略了等待。
2021-03-22 04:46:59
您可以使用 async/await 很好地处理部分结果,请参阅stackoverflow.com/a/42158854/2019689
2021-03-26 04:46:59
干净但要注意 Promise.all 的快速失败行为。如果任何函数抛出错误,Promise.all 将拒绝
2021-03-27 04:46:59
专业提示:使用数组解构来初始化来自 Promise.all() 的任意数量的结果,例如: [result1, result2] = Promise.all([async1(), async2()]);
2021-03-31 04:46:59

TL; 博士

使用Promise.all的并行函数调用,答案的行为不正确时出现错误。


首先,一次执行所有异步调用并获取所有Promise对象。第二,awaitPromise对象使用这样,在您等待第一个Promise解析的同时,其他异步调用仍在进行中。总的来说,您只会等待最慢的异步调用。例如:

// Begin first call and store promise without waiting
const someResult = someCall();

// Begin second call and store promise without waiting
const anotherResult = anotherCall();

// Now we await for both results, whose async processes have already been started
const finalResult = [await someResult, await anotherResult];

// At this point all calls have been resolved
// Now when accessing someResult| anotherResult,
// you will have a value instead of a promise

JSbin 示例:http ://jsbin.com/xerifanima/edit?js,console

警告:await调用是在同一行还是在不同的行上都没有关系,只要第一个await调用发生所有异步调用之后即可。请参阅 JohnnyHK 的评论。


更新:这个回答有错误不同时间处理根据@ BERGI的答案,但它不是为发生错误扔出去的错误,但所有的Promise都执行之后。我将结果与@jonny 的提示进行比较:[result1, result2] = Promise.all([async1(), async2()]),检查以下代码片段

const correctAsync500ms = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 500, 'correct500msResult');
  });
};

const correctAsync100ms = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 100, 'correct100msResult');
  });
};

const rejectAsync100ms = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 100, 'reject100msError');
  });
};

const asyncInArray = async (fun1, fun2) => {
  const label = 'test async functions in array';
  try {
    console.time(label);
    const p1 = fun1();
    const p2 = fun2();
    const result = [await p1, await p2];
    console.timeEnd(label);
  } catch (e) {
    console.error('error is', e);
    console.timeEnd(label);
  }
};

const asyncInPromiseAll = async (fun1, fun2) => {
  const label = 'test async functions with Promise.all';
  try {
    console.time(label);
    let [value1, value2] = await Promise.all([fun1(), fun2()]);
    console.timeEnd(label);
  } catch (e) {
    console.error('error is', e);
    console.timeEnd(label);
  }
};

(async () => {
  console.group('async functions without error');
  console.log('async functions without error: start')
  await asyncInArray(correctAsync500ms, correctAsync100ms);
  await asyncInPromiseAll(correctAsync500ms, correctAsync100ms);
  console.groupEnd();

  console.group('async functions with error');
  console.log('async functions with error: start')
  await asyncInArray(correctAsync500ms, rejectAsync100ms);
  await asyncInPromiseAll(correctAsync500ms, rejectAsync100ms);
  console.groupEnd();
})();

这个答案完全错误,应该删除。
2021-03-11 04:46:59
对我来说,这看起来是一个比 Promise.all 更好的选择——通过解构赋值,你甚至可以[someResult, anotherResult] = [await someResult, await anotherResult]在更改constlet.
2021-03-23 04:46:59
@Haven 此解决方案与Promise.all. 如果每个请求都是一次网络调用,await someResult则需要在await anotherResult启动之前解决反之,在Promise.all两个await调用之前,可以在其中任何一个被解析之前启动。
2021-03-31 04:46:59
这个答案具有误导性,因为两个等待都在同一行中完成是无关紧要的。重要的是在等待任何一个之前进行两个异步调用。
2021-04-02 04:46:59
但这仍然是await串行执行语句,对吗?也就是说,执行暂停直到第一个await解析,然后移动到第二个。Promise.all并行执行。
2021-04-07 04:46:59

更新:

最初的答案使得正确处理Promise拒绝变得困难(在某些情况下是不可能的)。正确的解决方案是使用Promise.all

const [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);

原答案:

只要确保在等待任何一个函数之前都调用了这两个函数:

// Call both functions
const somePromise = someCall();
const anotherPromise = anotherCall();

// Await both promises    
const someResult = await somePromise;
const anotherResult = await anotherPromise;
我会在代码中添加注释,因为没有什么说下一个开发人员会理解你在做什么 OOB。
2021-03-11 04:46:59
这个答案比 Haven 的要清楚得多。很明显,函数调用将返回Promise对象,然后await将它们解析为实际值。
2021-03-18 04:46:59
感觉这肯定是最纯粹的回答了
2021-04-01 04:46:59
这似乎粗略地工作,但在未经处理的拒绝方面存在可怕的问题不要使用这个!
2021-04-07 04:46:59
@Bergi 你是对的,谢谢你指出这一点!我已经用更好的解决方案更新了答案。
2021-04-09 04:46:59

还有另一种没有 Promise.all() 的方法可以并行执行:

首先,我们有两个函数来打印数字:

function printNumber1() {
   return new Promise((resolve,reject) => {
      setTimeout(() => {
      console.log("Number1 is done");
      resolve(10);
      },1000);
   });
}

function printNumber2() {
   return new Promise((resolve,reject) => {
      setTimeout(() => {
      console.log("Number2 is done");
      resolve(20);
      },500);
   });
}

这是顺序的:

async function oneByOne() {
   const number1 = await printNumber1();
   const number2 = await printNumber2();
} 
//Output: Number1 is done, Number2 is done

这是平行的:

async function inParallel() {
   const promise1 = printNumber1();
   const promise2 = printNumber2();
   const number1 = await promise1;
   const number2 = await promise2;
}
//Output: Number2 is done, Number1 is done
这是危险的,promise2promise1解决之前可能会拒绝如果发生这种情况,您将无法从 promise1 中捕获错误。在此答案中使用顺序模式,或使用Promise.all([printNumber1(), printNumber2()])
2021-03-14 04:46:59
你不能处理调用异步函数的错误吗?对我来说,这似乎更容易为.catch每件事单独添加一个,然后是Promise.all上面答案
2021-04-05 04:46:59

我创建了一个 gist,测试了一些解决Promise的不同方法,并给出了结果。查看有效的选项可能会有所帮助。

编辑:根据Jin Lee的评论要点内容

// Simple gist to test parallel promise resolution when using async / await

function promiseWait(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(true);
    }, time);
});
}


async function test() {
    return [
    await promiseWait(1000),
    await promiseWait(5000),
    await promiseWait(9000),
    await promiseWait(3000),
    ]
}

async function test2() {
    return {
        'aa': await promiseWait(1000),
        'bb': await promiseWait(5000),
        'cc': await promiseWait(9000),
        'dd': await promiseWait(3000),
    }
}

async function test3() {
    return await {
        'aa': promiseWait(1000),
        'bb': promiseWait(5000),
        'cc': promiseWait(9000),
        'dd': promiseWait(3000),
    }
}

async function test4() {
    const p1 =  promiseWait(1000);
    const p2 =  promiseWait(5000);
    const p3 =  promiseWait(9000);
    const p4 =  promiseWait(3000);
    return {
        'aa': await p1,
        'bb': await p2,
        'cc': await p3,
        'dd': await p4,
    };
}

async function test5() {
    return await Promise.all([
                             await promiseWait(1000),
                             await promiseWait(5000),
                             await promiseWait(9000),
                             await promiseWait(3000),
                             ]);
}

async function test6() {
    return await Promise.all([
                             promiseWait(1000),
                             promiseWait(5000),
                             promiseWait(9000),
                             promiseWait(3000),
                             ]);
}

async function test7() {
    const p1 =  promiseWait(1000);
    const p2 =  promiseWait(5000);
    const p3 =  promiseWait(9000);
    return {
        'aa': await p1,
        'bb': await p2,
        'cc': await p3,
        'dd': await promiseWait(3000),
    };
}

let start = Date.now();

test().then((res) => {
    console.log('Test Done, elapsed', (Date.now() - start) / 1000, res);

    start = Date.now();
    test2().then((res) => {
        console.log('Test2 Done, elapsed', (Date.now() - start) / 1000, res);

        start = Date.now();
        test3().then((res) => {
            console.log('Test3 Done, elapsed', (Date.now() - start) / 1000, res);

            start = Date.now();
            test4().then((res) => {
                console.log('Test4 Done, elapsed', (Date.now() - start) / 1000, res);

                start = Date.now();
                test5().then((res) => {
                    console.log('Test5 Done, elapsed', (Date.now() - start) / 1000, res);

                    start = Date.now();
                    test6().then((res) => {
                        console.log('Test6 Done, elapsed', (Date.now() - start) / 1000, res);
                    });

                    start = Date.now();
                    test7().then((res) => {
                        console.log('Test7 Done, elapsed', (Date.now() - start) / 1000, res);
                    });
                });
            });

        });
    });

});
/*
Test Done, elapsed 18.006 [ true, true, true, true ]
Test2 Done, elapsed 18.009 { aa: true, bb: true, cc: true, dd: true }
Test3 Done, elapsed 0 { aa: Promise { <pending> },
  bb: Promise { <pending> },
  cc: Promise { <pending> },
  dd: Promise { <pending> } }
Test4 Done, elapsed 9 { aa: true, bb: true, cc: true, dd: true }
Test5 Done, elapsed 18.008 [ true, true, true, true ]
Test6 Done, elapsed 9.003 [ true, true, true, true ]
Test7 Done, elapsed 12.007 { aa: true, bb: true, cc: true, dd: true }
*/
@JinLee 根据你的建议,我已经添加了内容,最初没有添加,因为它看起来内容很多
2021-03-11 04:46:59
虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会无效。-来自评论
2021-03-14 04:46:59
要点中的测试 4 和 6 返回了预期结果。请参阅NoNameProvided 的stackoverflow.com/a/42158854/5683904,他解释了选项之间的区别。
2021-03-24 04:46:59
@SkarXa SO 现在会更喜欢你的答案。:) 而且你的代码没有那么长。别担心。谢谢!
2021-03-31 04:46:59