提问人:Victor Marchuk 提问时间:2/25/2016 最后编辑:David Ferenczy RogožanVictor Marchuk 更新时间:6/5/2023 访问量:533823
并行调用 async/await 函数
Call async/await functions in parallel
问:
据我了解,在 ES7/ES2016 中,在代码中放置多个 ' 的工作方式类似于使用 promise 链接,这意味着它们将一个接一个地执行,而不是并行执行。因此,例如,我们有以下代码:await
.then()
await someCall();
await anotherCall();
我是否正确理解了它,只有在完成后才会调用它?并行称呼它们最优雅的方式是什么?anotherCall()
someCall()
我想在 Node 中使用它,所以也许有一个带有异步库的解决方案?
编辑:我对这个问题中提供的解决方案不满意:由于异步生成器中非并行等待承诺而变慢,因为它使用生成器,我正在询问一个更一般的用例。
答:
您可以等待: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.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" }]
注意:如果您使用的操作在拒绝发生之前设法完成,则不会回滚,因此您可能需要处理这种情况。例如 如果您有 5 个操作,则 4 个快速、1 个慢速和慢速拒绝。那 4 操作可能已执行,因此可能需要回滚。在这种情况下,请考虑使用 while 它将提供哪些操作失败和哪些操作失败的确切详细信息。
Promise.all
Promise.allSettled
评论
[result1, result2] = Promise.all([async1(), async2()]);
= await Promise.all
TL;博士
用于并行函数调用时,发生错误时应答行为不正确。Promise.all
首先,一次执行所有异步调用并获取所有对象。其次,在物体上使用。这样,当您等待第一个问题解决时,其他异步调用仍在进行中。总体而言,您只会等待最慢的异步调用。例如:Promise
await
Promise
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
警告:无论调用是在同一条线路上还是在不同线路上,只要第一次调用发生在所有异步调用之后即可。请参阅 JohnnyHK 的评论。await
await
更新:根据@bergi的答案,此答案在错误处理中具有不同的时间,它不会在错误发生时抛出错误,而是在所有承诺执行之后抛出错误。
我将结果与@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();
})();
评论
[someResult, anotherResult] = [await someResult, await anotherResult]
const
let
await
await
Promise.all
Promise.all
await someResult
await anotherResult
Promise.all
await
new
start call starts
second call starts
我投票给:
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]
}
评论
else
更新:
原始答案使得正确处理承诺拒绝变得困难(在某些情况下是不可能的)。正确的解决方案是使用: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;
评论
await
我创建了一个辅助函数 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 earlier
temp.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());
//or
const [result1, result2] = await waitAll(someResult(), anotherResult());
//const [result1, result2] = await parallel(someResult(), anotherResult());
评论
for
我创建了一个要点,测试一些不同的解决承诺的方法,并取得了结果。查看有效的选项可能会有所帮助。
编辑:根据 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 }
*/
评论
还有另一种方法可以在没有 Promise.all() 的情况下并行执行此操作:
首先,我们有 2 个函数来打印数字:
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
评论
promise2
promise1
Promise.all([printNumber1(), printNumber2()])
.catch
Promise.all
// A generic test function that can be configured
// with an arbitrary delay and to either resolve or reject
const test = (delay, resolveSuccessfully) => new Promise((resolve, reject) => setTimeout(() => {
console.log(`Done ${ delay }`);
resolveSuccessfully ? resolve(`Resolved ${ delay }`) : reject(`Reject ${ delay }`)
}, delay));
// Our async handler function
const handler = async () => {
// Promise 1 runs first, but resolves last
const p1 = test(10000, true);
// Promise 2 run second, and also resolves
const p2 = test(5000, true);
// Promise 3 runs last, but completes first (with a rejection)
// Note the catch to trap the error immediately
const p3 = test(1000, false).catch(e => console.log(e));
// Await all in parallel
const r = await Promise.all([p1, p2, p3]);
// Display the results
console.log(r);
};
// Run the handler
handler();
/*
Done 1000
Reject 1000
Done 5000
Done 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 result
console.log(result);
},
async () => {
var result = await wait(500, 'taco');
// do something with result
console.log(result);
},
async () => {
var result = await wait(5000, 'burp');
// do something with result
console.log(result);
}
]
await Promise.all(tasks.map(p => p()));
console.log('done');
输出:
Starting task: moose 1000
Starting task: taco 500
Starting task: burp 5000
taco
moose
burp
done
(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 result
console.log(result);
},
async () => {
var result = await wait(500, 'taco');
// do something with result
console.log(result);
},
async () => {
var result = await wait(5000, 'burp');
// do something with result
console.log(result);
}
]
await Promise.all(tasks.map(p => p()));
console.log('done');
})();
等待 Promise.all([someCall(), anotherCall()]);如前所述,它将充当线程围栏(在 CUDA 并行代码中非常常见),因此它将允许其中的所有承诺在不阻塞彼此的情况下运行,但会阻止执行继续,直到 ALL 得到解决。
另一种值得分享的方法是 Node.js 异步,它还允许您轻松控制通常需要的并发量,如果任务直接链接到 API 调用、I/O 操作等有限资源的使用。
// create a queue object with concurrency 2
var q = async.queue(function(task, callback) {
console.log('Hello ' + task.name);
callback();
}, 2);
// assign a callback
q.drain = function() {
console.log('All items have been processed');
};
// add some items to the queue
q.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 queue
q.unshift({name: 'bar'}, function (err) {
console.log('Finished processing bar');
});
感谢 Medium 文章 autor (阅读更多)
评论
您可以调用多个异步函数,而无需等待它们。这将并行执行它们。在执行此操作时,将返回的 promise 保存在变量中,并在某个时间点单独或使用 Promise.all() 等待它们并处理结果。
您还可以使用 try 包装函数调用...catch 用于处理单个异步操作的失败并提供回退逻辑。
下面是一个示例: 观察日志,在各个异步函数执行开始时打印的日志会立即打印出来,即使第一个函数需要 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, anotherLongFuncPromise
const 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 someLongFuncPromise
await anotherLongFuncPromise
const totalTime = Date.now() - start
console.log('Execution completed in ', totalTime)
}
main()
这可以通过 Promise.allSettled() 来实现,它类似于 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!"}
注意:这是一个前沿功能,浏览器支持有限,因此我强烈建议为此功能添加 polyfill。
评论
await
Promise