并行调用 async/await 函数

Call async/await functions in parallel

提问人:Victor Marchuk 提问时间:2/25/2016 最后编辑:David Ferenczy RogožanVictor Marchuk 更新时间:6/5/2023 访问量:533823

问:

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

await someCall();
await anotherCall();

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

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

编辑:我对这个问题中提供的解决方案不满意:由于异步生成器中非并行等待承诺而变慢,因为它使用生成器,我正在询问一个更一般的用例。

JavaScript 节点.js 异步 ecmascript-6 babeljs

评论

7赞 adeneo 2/25/2016
@Blindman67 - 它确实如此,至少 OP 的意思是,两个异步操作同时运行,但不是在这种情况下,我的意思是写它们是串行运行的,第一个会等待第一个函数完全完成,然后再执行第二个。await
4赞 adeneo 2/25/2016
@Blindman67 - 它是单线程的,但该限制不适用于异步方法,它们可以同时运行,并在完成后返回响应,即 OP 所说的“并行”是什么意思。
12赞 adeneo 2/25/2016
@Blindman67 - 我认为很清楚 OP 的要求,使用 async/await 模式将使函数以串行方式运行,即使它们是异步的,所以第一个会在第二个被调用之前完全完成,等等。OP 询问如何并行调用这两个函数,由于它们显然是异步的,因此目的是同时运行它们,即并行运行,例如同时执行两个 ajax 请求,这在 javascript 中根本不是问题,因为正如您所指出的,大多数异步方法运行本机代码并使用更多线程。
4赞 Iest 5/10/2017
@Bergi这不是链接问题的重复 - 这是专门关于 async/await 语法和本机 s 的。相关的问题是关于带有生成器和产量的蓝鸟库。也许在概念上相似,但在实现上不是。Promise
3赞 Iest 5/12/2017
@Bergi 语法确实很重要。对于从未使用过发电机或蓝鸟的人来说,链接的问题是完全没有帮助的。

答:

1374赞 madox2 2/25/2016 #1

您可以等待: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.allPromise.allSettled

评论

110赞 NoNameProvided 2/8/2017
清理,但要注意 Promise.all 的快速失败行为。如果任何函数抛出错误,Promise.all 将拒绝
13赞 NoNameProvided 3/27/2017
你可以用 async/await 很好地处理部分结果,参见 stackoverflow.com/a/42158854/2019689
160赞 jonny 2/10/2018
专业提示:使用数组解构来初始化来自 Promise.all() 的任意数量的结果,例如:[result1, result2] = Promise.all([async1(), async2()]);
13赞 theUtherSide 7/12/2018
@jonny 这是否受制于快速故障?另外,还需要吗?= await Promise.all
7赞 jonny 7/12/2018
@theUtherSide 你是绝对正确的 - 我忽略了等待。
167赞 Haven 8/19/2017 #2

TL;博士

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


首先,一次执行所有异步调用并获取所有对象。其次,在物体上使用。这样,当您等待第一个问题解决时,其他异步调用仍在进行中。总体而言,您只会等待最慢的异步调用。例如:PromiseawaitPromisePromise

// 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 的评论。awaitawait


更新:根据@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();
})();

评论

13赞 jawj 8/25/2017
在我看来,这看起来是一个比 Promise.all 更好的选择——而且通过解构赋值,如果您更改为 .[someResult, anotherResult] = [await someResult, await anotherResult]constlet
35赞 Andru 8/26/2017
但这仍然是按顺序执行语句的,对吧?也就是说,执行会暂停,直到第一个解析,然后移动到第二个。 并行执行。awaitawaitPromise.all
99赞 JohnnyHK 10/16/2017
这个答案具有误导性,因为两个等待都在同一行中完成这一事实是无关紧要的。重要的是,在等待任何一个异步调用之前,都会进行这两个异步调用。
21赞 Ben Winding 2/7/2018
@Haven此解决方案与 不同。如果每个请求都是网络调用,则需要在启动之前进行解析。相反,在这两个调用中,可以在解决任何一个调用之前启动。Promise.allawait someResultawait anotherResultPromise.allawait
6赞 Anand N 2/26/2018
答案具有误导性。jsbin 代码似乎在并行执行 promise,但事实并非如此。使用 operator 创建 promise 时,构造函数将同步调用。这就是我们立即看到的原因。newstart call startssecond call starts
-5赞 Hoang Le Anh Tu 9/28/2017 #3

我投票给:

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]
}

评论

0赞 Haven 9/29/2017
由于您在条件测试之外/之前声明了函数,并调用了它们。尝试将它们包裹在块中。else
0赞 Hoang Le Anh Tu 12/3/2017
@Haven:我的意思是,当你将调用函数的时刻与 await 分开时,可能会导致意想不到的结果,例如:异步 HTTP 请求。
128赞 Jonathan Potter 12/21/2017 #4

更新:

原始答案使得正确处理承诺拒绝变得困难(在某些情况下是不可能的)。正确的解决方案是使用: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;

评论

0赞 Jeff Fischer 1/8/2018
我会在代码中添加注释,因为没有说下一个开发人员会理解你在做什么 OOB。
11赞 Gershom Maes 4/18/2018
我觉得这当然是最纯粹的答案
1赞 WSBT 9/5/2018
这个答案比 Haven 的要清楚得多。很明显,函数调用将返回 promise 对象,然后将它们解析为实际值。await
8赞 Bergi 1/21/2019
这似乎可以粗略地看一看,但存在未处理的拒绝的可怕问题不要用这个!
1赞 Jonathan Potter 3/12/2020
@Bergi 你是对的,谢谢你指出来!我已经用更好的解决方案更新了答案。
-5赞 Fred Yang 2/8/2018 #5

我创建了一个辅助函数 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());

评论

3赞 Szczepan Hołyszewski 7/24/2018
不,这里根本没有发生并行化。循环按顺序等待每个 promise 并将结果添加到数组中。for
0赞 Fred Yang 2/16/2019
我知道这似乎对人们不起作用。所以我在 node.js 和浏览器中进行了测试。该测试在node.js(v10,v11),firefox中通过,它在浏览器chrome中不起作用。测试用例处于 gist.github.com/fredyang/ea736a7b8293edf7a1a25c39c7d2fbbf
2赞 Szczepan Hołyszewski 2/19/2019
我拒绝相信这一点。标准中没有任何内容说 for 循环的不同迭代可以自动并行化;这不是 JavaScript 的工作方式。循环代码的编写方式,它的意思是:“await one item (the await expr),THEN push result to temp, THEN take next item(for 循环的下一次迭代)。每个项目的“等待”完全局限于循环的单次迭代。如果测试表明存在并行化,那一定是因为转译器正在做一些非标准的事情,或者完全有问题。
0赞 Fred Yang 2/21/2019
@SzczepanHo łyszewski:你对不运行测试用例的信心激励我做一些重命名、重新工厂和额外的评论。所有代码都是普通的旧 ES6,不需要转译。
0赞 Jonathan Sudiaman 4/6/2020
不知道为什么这被如此严重地否决了。这基本上和@user2883596给出的答案是一样的。
24赞 SkarXa 9/25/2018 #6

我创建了一个要点,测试一些不同的解决承诺的方法,并取得了结果。查看有效的选项可能会有所帮助。

编辑:根据 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 }
*/

评论

0赞 akraines 10/4/2018
要点中的测试 4 和 6 返回了预期结果。请参阅 NoNameProvided stackoverflow.com/a/42158854/5683904,其中解释了选项之间的区别。
0赞 Jin Lee 3/3/2021
虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接的页面发生更改,仅链接的答案可能会失效。- 从评论
0赞 SkarXa 3/3/2021
@JinLee 根据您的建议,我添加了内容,最初没有添加它,因为它看起来很多内容
1赞 Jin Lee 3/4/2021
@SkarXa SO 现在会更喜欢你的答案。:)而且你的代码没有那么长。不用担心。谢谢!
46赞 MMMM 2/27/2019 #7

还有另一种方法可以在没有 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

评论

3赞 oldwizard 11/18/2020
这很危险,可能会在解决之前拒绝。如果发生这种情况,则无法从 promise1 捕获错误。在此答案中使用顺序模式,或使用promise2promise1Promise.all([printNumber1(), printNumber2()])
2赞 Hart Simha 5/16/2021
您无法处理调用异步函数的错误吗?对我来说,这似乎使单独添加一个比上面的答案更容易.catchPromise.all
4赞 Thrunobulax 7/3/2019 #8
    // 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 并不是严格地并行运行它们,但它们不会阻碍任何执行,并且您可以使用捕获来捕获上下文错误。

评论

2赞 Theo 7/3/2019
欢迎使用 Stack Overflow。虽然您的代码可能会提供问题的答案,但请在它周围添加上下文,以便其他人知道它的作用以及它为什么在那里。
23赞 Alex Dresko 10/7/2019 #9

就我而言,我有几个任务要并行执行,但我需要对这些任务的结果做一些不同的事情。

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');
})();

8赞 Thiago Conrado 5/18/2020 #10

等待 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 (阅读更多)

评论

0赞 Matt 7/24/2020
bluebirds .map、.filter 和 .mapSeries 也有助于并发,如果您不想引入异步模块回调 API
7赞 thisispraveenk 9/13/2021 #11

您可以调用多个异步函数,而无需等待它们。这将并行执行它们。在执行此操作时,将返回的 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()

4赞 KARTHIKEYAN.A 8/23/2022 #12

这可以通过 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。