并行调用async/wait函数

据我所知,在ES7/ES2016中,在代码中放入多个await的工作方式类似于将.then()与Promise链接起来,这意味着它们将一个接一个地执行,而不是并行执行。所以,例如,我们有这段代码:

await someCall();await anotherCall();

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

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

编辑:我对这个问题中提供的解决方案不满意:由于异步生成器中的Promise的非并行等待而减速,因为它使用了生成器,我问的是一个更通用的用例。

456052 次浏览

您可以在Promise.all()上等待:

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

存储结果:

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

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

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.allSettled,同时它将提供哪些操作失败而哪些操作没有失败的确切细节。

太长别读

使用Promise.all进行并行函数调用,错误发生时应答行为不正确。


首先,立即执行所有异步调用并获取所有Promise对象。其次,在Promise对象上使用await。这样,当你等待第一个Promise来解决其他异步调用时,其他异步调用仍在进行中。总的来说,你只会等待最慢的异步调用。例如:

// Begin first call and store promise without waitingconst someResult = someCall();
// Begin second call and store promise without waitingconst anotherResult = anotherCall();
// Now we await for both results, whose async processes have already been startedconst 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控制台

警告:await调用是在同一行还是在不同行并不重要,只要第一个await调用发生之后所有异步调用。参见JohnnyHK的评论。


更新时间:这个答案根据@贝吉的回答在错误处理方面有不同的时间,它确实不是在错误发生时抛出错误,但在所有承诺执行之后。我将结果与@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();})();

我投票赞成:

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

注意你调用函数的那一刻,可能会导致意想不到的结果:

// Supposing anotherCall() will trigger a request to create a new User
if (callFirst) {await someCall();} else {await Promise.all([someCall(), anotherCall()]); // --> create new User here}

但以下总是触发创建新用户的请求

// Supposing anotherCall() will trigger a request to create a new User
const someResult = someCall();const anotherResult = anotherCall(); // ->> This always creates new User
if (callFirst) {await someCall();} else {const finalResult = [await someResult, await anotherResult]}

更新时间:

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

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

原答复:

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

// Call both functionsconst somePromise = someCall();const anotherPromise = anotherCall();
// Await both promisesconst someResult = await somePromise;const anotherResult = await anotherPromise;

我创建了一个辅助函数waitAll,也许它可以让它更甜。目前它只适用于nodejs,浏览器chrome中的没有

    //const parallel = async (...items) => {const waitAll = async (...items) => {//this function does start execution the functions//the execution has been started before running this code here//instead it collects of the result of execution of the functions
const temp = [];for (const item of items) {//this is not//temp.push(await item())//it does wait for the result in series (not in parallel), but//it doesn't affect the parallel execution of those functions//because they haven started earliertemp.push(await item);}return temp;};
//the async functions are executed in parallel before passed//in the waitAll function
//const finalResult = await waitAll(someResult(), anotherResult());//const finalResult = await parallel(someResult(), anotherResult());//orconst [result1, result2] = await waitAll(someResult(), anotherResult());//const [result1, result2] = await parallel(someResult(), anotherResult());

我已经创建了一个要点来测试一些不同的解决Promise的方法,并带来了结果。

编辑:根据李进的评论列出要点内容

// 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 }*/

还有另一种不用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
    // A generic test function that can be configured// with an arbitrary delay and to either resolve or rejectconst test = (delay, resolveSuccessfully) => new Promise((resolve, reject) => setTimeout(() => {console.log(`Done ${ delay }`);resolveSuccessfully ? resolve(`Resolved ${ delay }`) : reject(`Reject ${ delay }`)}, delay));
// Our async handler functionconst handler = async () => {// Promise 1 runs first, but resolves lastconst p1 = test(10000, true);// Promise 2 run second, and also resolvesconst p2 = test(5000, true);// Promise 3 runs last, but completes first (with a rejection)// Note the catch to trap the error immediatelyconst p3 = test(1000, false).catch(e => console.log(e));// Await all in parallelconst r = await Promise.all([p1, p2, p3]);// Display the resultsconsole.log(r);};
// Run the handlerhandler();/*Done 1000Reject 1000Done 5000Done 10000*/

虽然设置p1、p2和p3并不严格地并行运行它们,但它们不会阻碍任何执行,并且您可以使用捕获来捕获上下文错误。

在我的例子中,我有几个我想并行执行的任务,但我需要对这些任务的结果做一些不同的事情。

function wait(ms, data) {console.log('Starting task:', data, ms);return new Promise(resolve => setTimeout(resolve, ms, data));}
var tasks = [async () => {var result = await wait(1000, 'moose');// do something with resultconsole.log(result);},async () => {var result = await wait(500, 'taco');// do something with resultconsole.log(result);},async () => {var result = await wait(5000, 'burp');// do something with resultconsole.log(result);}]
await Promise.all(tasks.map(p => p()));console.log('done');

以及输出:

Starting task: moose 1000Starting task: taco 500Starting task: burp 5000tacomooseburpdone

(async function(){function wait(ms, data) {console.log('Starting task:', data, ms);return new Promise(resolve => setTimeout(resolve, ms, data));}
var tasks = [async () => {var result = await wait(1000, 'moose');// do something with resultconsole.log(result);},async () => {var result = await wait(500, 'taco');// do something with resultconsole.log(result);},async () => {var result = await wait(5000, 'burp');// do something with resultconsole.log(result);}]
await Promise.all(tasks.map(p => p()));console.log('done');})();

WAIITPromise.all([一些调用(),另一个调用()]);如前所述,它将充当线程围栏(在CUDA并行代码中非常常见),因此它将允许其中的所有Promise在不相互阻塞的情况下运行,但将阻止执行继续,直到所有的都被解决。

另一种值得分享的方法是Node.js异步,它还允许您轻松控制并发量,如果任务直接与API调用、I/O操作等有限资源的使用相关联,则通常需要这种并发量。

// create a queue object with concurrency 2var q = async.queue(function(task, callback) {console.log('Hello ' + task.name);callback();}, 2);
// assign a callbackq.drain = function() {console.log('All items have been processed');};
// add some items to the queueq.push({name: 'foo'}, function(err) {console.log('Finished processing foo');});
q.push({name: 'bar'}, function (err) {console.log('Finished processing bar');});
// add some items to the queue (batch-wise)q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function(err) {console.log('Finished processing item');});
// add some items to the front of the queueq.unshift({name: 'bar'}, function (err) {console.log('Finished processing bar');});

信用媒体文章自动(阅读更多

你可以调用多个异步函数而无需等待它们。这将并行执行它们。这样做的同时,将返回的Promise保存在变量中,并在某个时候单独或使用Promise.all()等待它们并处理结果。

您还可以使用try… cat包装函数调用,以处理单个异步操作的失败并提供兜底逻辑。

举个例子:观察日志,在各个异步函数执行开始时打印的日志会立即打印出来,即使第一个函数需要5秒才能解决。

function someLongFunc () {return new Promise((resolve, reject)=> {console.log('Executing function 1')setTimeout(resolve, 5000)})}
function anotherLongFunc () {return new Promise((resolve, reject)=> {console.log('Executing function 2')setTimeout(resolve, 5000)})}
async function main () {let someLongFuncPromise, anotherLongFuncPromiseconst start = Date.now()try {someLongFuncPromise = someLongFunc()}catch (ex) {console.error('something went wrong during func 1')}try {anotherLongFuncPromise = anotherLongFunc()}catch (ex) {console.error('something went wrong during func 2')}
await someLongFuncPromiseawait anotherLongFuncPromiseconst totalTime = Date.now() - startconsole.log('Execution completed in ', totalTime)}
main()

这可以用Promise.all定居()来完成,它类似于Promise.all(),但没有快速失败的行为。

async function Promise1() {throw "Failure!";}
async function Promise2() {return "Success!";}
const [Promise1Result, Promise2Result] = await Promise.allSettled([Promise1(), Promise2()]);
console.log(Promise1Result); // {status: "rejected", reason: "Failure!"}console.log(Promise2Result); // {status: "fulfilled", value: "Success!"}

注意:这是一个前沿功能,对浏览器的支持有限,所以我强烈建议为这个功能包含一个Poly填充。