并行调用异步/等待功能


431

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

await someCall();
await anotherCall();

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

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

编辑:我不满意此问题中提供的解决方案:减速是由于异步生成器中非并行等待promise,因为它使用生成器,并且我询问的是更通用的用例。


1
@adeneo这是不正确的,JavaScript永远不会在其自身的上下文中并行运行。
Blindman67

5
@ Blindman67-这样做,至少是按照OP的方式,其中两个异步操作同时运行,但在这种情况下,我要写的是它们是串行运行的,第一个await将等待第一个功能完成完全在执行第二个之前。
adeneo '16

3
@ Blindman67-它是单线程的,但是该限制不适用于异步方法,它们可以同时运行,并在完成后返回响应,即OP的“并行”含义。
adeneo '16

7
@ Blindman67-我认为很清楚,OP正在询问什么,使用async / await模式将使函数以串行方式运行,即使它们是异步的,因此第一个将在第二个被调用之前完全完成,等等。询问如何并行调用两个函数,并且它们显然是异步的,所以目的是同时运行它们,即并行运行,例如同时执行两个ajax请求,这在javascript中根本不是问题,因为大多数异步方法如您所指出的,它运行本机代码,并使用更多线程。
adeneo '16

3
@Bergi,这不是链接问题的重复-这是专门关于async / await语法和native Promise的。链接的问题是关于带有生成器和收益的bluebird库。在概念上可能相似,但在实现上不相似。
IEST

Answers:


698

您可以等待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" }]


78
清理但要注意Promise.all的快速​​失败行为。如果任何函数引发错误,Promise.all将拒绝
NoNameProvided '17

11
您可以使用async / await很好地处理部分结果,请参阅stackoverflow.com/a/42158854/2019689
NoName17年

131
临提示:为了初始化结果从Promise.all(任意数量的使用阵列解构),如:[result1, result2] = Promise.all([async1(), async2()]);
强尼

10
@jonny这是否会导致快速失败?另外,还需要= await Promise.all吗?
theUtherSide

5
@theUtherSide你是完全正确的-我忽略了等待。
jonny

114

TL; DR

使用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的答案,但它不是为发生错误扔出去的错误,但所有的承诺都执行之后。我将结果与@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();
})();


11
对于我来说,这似乎比Promise.all更好,而且通过解构分配,即使[someResult, anotherResult] = [await someResult, await anotherResult]更改const为,您也可以执行let
颚j17年

28
但这仍然会顺序执行await语句,对吗?也就是说,执行会暂停直到第一个await解决,然后再移至第二个。Promise.all并行执行。
Andru

8
谢谢@Haven。这应该是公认的答案。
Stefan D

87
这个答案是误导性的,因为两个等待都在同一行中完成是不相关的。重要的是在等待两个异步调用之前先进行两个异步调用。
JohnnyHK

15
@Haven此解决方案与Promise.all。如果每个请求都是网络呼叫,则甚至await someResult需要在await anotherResult开始之前进行解决。相反,在Promise.all两个await呼叫中的任何一个都可以解决之前就可以开始。
本·温丁

89

更新:

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

1
@JeffFischer我添加了一些评论,希望可以使其更加清晰。
乔纳森·波特

9
我觉得这当然是最纯粹的答案
Gershom '18

1
这个答案比避风港的要清楚得多。显然,函数调用将返回promise对象,然后await将其解析为实际值。
user1032613

3
乍看之下似乎很有效,但是由于未处理的拒绝存在可怕的问题不要使用这个!
Bergi

1
@Bergi你说得对,谢谢你指出这一点!我已经用更好的解决方案更新了答案。
乔纳森·波特

24

没有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

10

这可以通过Promise.allSettled()来完成,类似于Promise.all()但不具有快速失败行为。

async function failure() {
    throw "Failure!";
}

async function success() {
    return "Success!";
}

const [failureResult, successResult] = await Promise.allSettled([failure(), success()]);

console.log(failureResult); // {status: "rejected", reason: "Failure!"}
console.log(successResult); // {status: "fulfilled", value: "Success!"}

注意:这是浏览器支持有限的最新功能,因此我强烈建议为此功能包括一个polyfill。



1
    // 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并不是严格并行运行它们,但是它们不会阻止任何执行,因此您可以使用catch捕获上下文错误。


2
欢迎使用堆栈溢出。虽然您的代码可以提供问题的答案,但是请在其周围添加上下文,以便其他人可以知道它的作用以及存在的原因。
西奥,

1

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

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

动态创建(资源阵列)很酷
Michal MikyJankovský

1

等待Promise.all([someCall(),anotherCall()]); 就像已经提到的那样,它将充当线程屏障(在CUDA中非常常见于并行代码中),因此它将允许其中的所有诺言都能彼此不阻塞地运行,但是将阻止执行继续,直到解决所有问题为止。

值得分享的另一种方法是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');
});

归功于中篇文章的导师(了解更多


-5

我投赞成票:

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块。
天堂

@Haven:我的意思是,当您分开调用函数与等待时,可能会导致意外结果,例如:异步HTTP请求。
Hoang Le Anh Tu

-6

我创建了一个辅助函数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
不,并行化在这里根本没有发生。该for循环顺序等待每个承诺并将结果添加到阵列。
SzczepanHołyszewski18年

我了解这似乎不适用于人们。因此,我在node.js和浏览器中进行了测试。该测试是通过node.js(v10,v11),firefox通过的,它不适用于浏览器chrome。测试案例在gist.github.com/fredyang/ea736a7b8293edf7a1a25c39c7d2fbbf
Fred Yang

2
我拒绝相信这一点。标准中没有什么可以说for循环的不同迭代可以自动并行化的。这不是javascript的工作方式。编写循环代码的方式表示:“等待一个项目(await expr),然后将结果推送到temp,然后获取下一个项目(for循环的下一个迭代)。每个项目的“等待”是完全局限在循环的单个迭代。如果测试表明,有并行,那一定是因为transpiler是做一些非标准或者是平了马车。
SzczepanHołyszewski

@SzczepanHołyszewski您在不运行测试用例的情况下就放任自流的信心鼓舞了我进行一些重命名重构和其他注释。所有代码都是普通的旧版ES6,无需转译。
Fred Yang

不知道为什么要这么严重地投票。它与@ user2883596给出的答案基本相同。
乔纳森·苏迪亚曼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.