并行调用async/await函数

Vic*_*huk 347 javascript asynchronous node.js ecmascript-6 babeljs

据我所知,在ES7/ES2016中,将多个await代码放在代码中将类似于.then()使用promise 进行链接,这意味着它们将一个接一个地执行,而不是在并行执行.所以,例如,我们有这个代码:

await someCall();
await anotherCall();
Run Code Online (Sandbox Code Playgroud)

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

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

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

mad*_*ox2 536

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

await Promise.all([someCall(), anotherCall()]);
Run Code Online (Sandbox Code Playgroud)

  • 专业提示:使用数组解构来初始化来自Promise.all()的任意数量的结果,如:`[result1,result2] = Promise.all([async1(),async2()]); (119认同)
  • 干净但要注意Promise.all的快速​​失败行为.如果任何函数抛出错误,Promise.all将拒绝 (58认同)
  • 您可以使用async/await很好地处理部分结果,请参阅http://stackoverflow.com/a/42158854/2019689 (10认同)
  • @jonny这个主题是否快速失败?还有,还需要`= await Promise.all`吗? (10认同)
  • @theUtherSide你是完全正确的 - 我忽略了包括等待. (5认同)
  • @jonny不必学究,但您还需要使用`let`,`const`或`var`声明变量,如下所示:`let [result1,result2] =等待Promise.all([async1( ),async2()]);` (4认同)
  • 如果您希望Promise.all的单线失败,请使用const results = await Promise.all(promises.map(promise => promise.catch(error => error))));`一系列的承诺。从技术上讲,现在一切都可以解决了,要检查是否有失败,请遍历“结果”并检查item是否为“ instanceof Error”。(假设您拒绝并显示错误)。 (2认同)

Hav*_*ven 98

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
Run Code Online (Sandbox Code Playgroud)

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();
})();
Run Code Online (Sandbox Code Playgroud)

  • 这个答案具有误导性,因为两个等待都是在同一条线上完成的事实是无关紧要的.重要的是两个异步调用是在等待之前进行的. (86认同)
  • 但这仍然是串行执行`await`语句,对吧?也就是说,执行暂停直到第一个`await`结算,然后移到第二个.`Promise.all`并行执行. (27认同)
  • @Haven这个解决方案与`Promise.all`不同.如果每个请求都是一个网络调用,那么`await someResult`将需要在`await anotherResult`开始之前解决.相反,在`Promise.all`中,两个`await`调用可以在任何一个被解析之前启动. (14认同)
  • 对于我而言,这看起来比Promise.all更好 - 并且通过解构赋值你甚至可以做`[someResult,anotherResult] = [等待someResult,等待另一个结果]`如果你将`const`改为`let`. (11认同)
  • 谢谢@Haven.这应该是公认的答案. (8认同)
  • 这个答案是完全错误的,应该删除. (6认同)
  • 答案是误导性的.jsbin代码似乎是并行执行promises,但它们不是.使用`new`运算符创建promise时,将同步调用构造函数.这就是我们立刻看到"开始呼叫开始"和"第二次呼叫开始"的原因. (5认同)
  • @Andru不,它并行运作.我添加了一个jsbin示例,你可以在那里查看. (4认同)
  • 粗略地看,这似乎有效,但是[在未处理的拒绝方面存在可怕的问题](/sf/ask/3282250331/)。**不要使用这个!** (3认同)
  • 虽然问题是如何并行调用函数,但这个答案确实给出了一个很好的例子,说明如何在执行剩余代码之前等待两个 Promise 解析。它们不是并行启动的,而是同时运行的。谢谢 (2认同)
  • @LawrenceCheuk是的,现在是正确的:)它们不是并行启动的,数组按顺序等待当然,在数组之前,所有它们的处理已经开始(几乎同时) (2认同)
  • 有一个陷阱。如果将整个语句放在try-catch块中,而第二个promise在第一个promise之前被拒绝,则将无法处理该异常。Promise.all更安全。 (2认同)
  • 正如其他人所说,这不是并行呼叫,而是顺序呼叫. (2认同)

Jon*_*ter 81

更新:

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

const [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);
Run Code Online (Sandbox Code Playgroud)

原始答案:

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

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

// Await both promises    
const someResult = await somePromise;
const anotherResult = await anotherPromise;
Run Code Online (Sandbox Code Playgroud)

  • 我觉得这肯定是最纯粹的答案 (9认同)
  • 乍一看似乎可以正常工作,但是[存在未处理的拒绝带来的可怕问题](/sf/ask/3282250331/)。**不要使用这个!** (3认同)

use*_*596 17

没有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);
   });
}
Run Code Online (Sandbox Code Playgroud)

这是顺序的:

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

这是并行的:

async function inParallel() {
   const promise1 = printNumber1();
   const promise2 = printNumber2();
   const number1 = await promise1;
   const number2 = await promise2;
}
//Output: Number2 is done, Number1 is done
Run Code Online (Sandbox Code Playgroud)

  • 这是危险的,在解决“promise1”之前,“promise2”可能会被拒绝。如果发生这种情况,您将无法捕获 Promise1 中的错误。要么使用这个答案中的顺序模式,要么使用`Promise.all([printNumber1(), printNumber2()])` (3认同)
  • 你不能处理调用异步函数的错误吗?对我来说,这似乎更容易为每个东西单独添加一个“.catch”,然后上面的“Promise.all”答案 (2认同)

Ale*_*sko 11

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

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');
Run Code Online (Sandbox Code Playgroud)

和输出:

Starting task: moose 1000
Starting task: taco 500
Starting task: burp 5000
taco
moose
burp
done
Run Code Online (Sandbox Code Playgroud)


小智 8

等待 Promise.all([someCall(), anotherCall()]); 正如已经提到的,它将充当线程围栏(在并行代码如 CUDA 中很常见),因此它将允许其中的所有承诺运行而不会相互阻塞,但会阻止执行继续,直到所有承诺都得到解决。

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

// 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');
});
Run Code Online (Sandbox Code Playgroud)

致谢 Medium 文章作者(阅读更多


小智 7

您可以调用多个异步函数而无需等待它们。这将并行执行它们。这样做时,将返回的 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()
Run Code Online (Sandbox Code Playgroud)


Ska*_*rXa 6

我已经创建了一个要点,用一些不同的方法来测试承诺和结果.查看有效的选项可能会有所帮助.


Qwe*_*rty 6

直观的解决方案

function wait(ms, data) {
  console.log('Starting task:', data, ms);
  return new Promise( resolve => setTimeout(resolve, ms, data) );
}

(async function parallel() {

  // step 1 - initiate all promises
  console.log('STARTING')
  let task1 = wait(2000, 'parallelTask1') // PS: see Exception handling below
  let task2 = wait(500, 'parallelTask2')
  let task3 = wait(1000, 'parallelTask3')

  // step 2 - await all promises
  console.log('WAITING')
  task1 = await task1
  task2 = await task2
  task3 = await task3

  // step 3 - all results are 100% ready
  console.log('FINISHED')
  console.log('Result:', task1, task2, task3)

})()
Run Code Online (Sandbox Code Playgroud)

  1. 这将一个接一个地执行承诺,但立即执行,它们将继续同时运行。
  2. 这是我们暂停进一步的代码执行并等待它们完成的地方。顺序无关紧要,先解决的是哪一个。在所有代码都解决之前,该代码将不会继续执行步骤3。如果第一个使用的时间最长,则不必再等待第二个,因为在代码到达那里之前它已经完成了。
  3. 完成,最后的承诺已解决,最后一次await调用完成了代码执行。


使用ES6,您甚至可以在执行启动后的第2步中执行此操作

[task1, task2, task3] = [await task1, await task2, await task3]
Run Code Online (Sandbox Code Playgroud)


PS:您也可以等待内部计算

let five = getAsyncFive()
let ten = getAsyncTen()

let result = await five * await ten
Run Code Online (Sandbox Code Playgroud)

*请注意,这与let result = await getAsyncFive() * await getAsyncTen()将不会并行运行异步任务不同!


异常处理

在下面的代码段中,.catch(e => e)捕获到错误并允许链继续运行,从而使诺言得以解决,而不是拒绝。没有catch,代码将引发未处理的异常,并且函数将提前退出。

[task1, task2, task3] = [await task1, await task2, await task3]
Run Code Online (Sandbox Code Playgroud)

第二个代码段未处理,功能将失败。
您也可以打开Devtools并在控制台输出中查看错误。

let five = getAsyncFive()
let ten = getAsyncTen()

let result = await five * await ten
Run Code Online (Sandbox Code Playgroud)