如何在 Javascript 中并行运行 async/await

IT技术 javascript async-await
2021-03-08 22:50:13

最后async/await很快在除 IE 之外的所有主要浏览器中得到支持所以现在我们可以开始用async/编写更具可读性的代码,await但有一个问题。很多人像这样使用异步等待:

const userResponse = await fetchUserAsync();
const postsResponse = await fetchPostsAsync();

虽然这段代码是可读的,但它有一个问题,它会连续运行这些函数,直到用户的获取完成它才会开始获取帖子。解决方案很简单,我们需要并行获取资源。

所以我想做的是(用伪语言):

fn task() {
  result-1 = doAsync();
  result-2 = doAsync();
  result-n = doLongAsync();

  // handle results together
  combinedResult = handleResults(result-1, result-2);

  lastResult = handleLastResult(result-n);
}
6个回答

你可以这样写:

const responses = await Promise.all([
 fetchUserAsync(),
 fetchPostsAsync(),
]);

const userResponse = responses[0];
const postsResponse = responses[1];

这很容易吧?但是有一个问题!Promise.all具有快速失败行为,这意味着,一旦其中一个Promise被拒绝,它就会拒绝。可能您想要一个更强大的解决方案,我们负责处理任何提取的拒绝。幸运的是,有一个解决方案,只需使用async/await即可实现,无需使用Promise.all. 一个工作示例:

console.clear();

function wait(ms, data) {
  return new Promise( resolve => setTimeout(resolve.bind(this, data), ms) );
}

/** 
 * This will run in series, because 
 * we call a function and immediately wait for it's result, 
 * so this will finish in 1s.
 */
async function series() {
  return {
    result1: await wait(500, 'seriesTask1'),
    result2: await wait(500, 'seriesTask2'),
  }
}

/** 
 * While here we call the functions first,
 * then wait for the result later, so 
 * this will finish in 500ms.
 */
async function parallel() {
  const task1 = wait(500, 'parallelTask1');
  const task2 = wait(500, 'parallelTask2');

  return {
    result1: await task1,
    result2: await task2,
  }
}

async function taskRunner(fn, label) {
  const startTime = performance.now();
  console.log(`Task ${label} starting...`);
  let result = await fn();
  console.log(`Task ${label} finished in ${ Number.parseInt(performance.now() - startTime) } miliseconds with,`, result);
}

void taskRunner(series, 'series');
void taskRunner(parallel, 'parallel');


/* 
 * The result will be:
 * Task series starting...
 * Task parallel starting...
 * Task parallel finished in 500 milliseconds with, { "result1": "parallelTask1", "result2": "parallelTask2" }
 * Task series finished in 1001 milliseconds with, { "result1": "seriesTask1", "result2": "seriesTask2" }
 */

注意:您将需要一个已async/await 启用的浏览器来运行此代码段(或 nodejs v7 及更高版本)

这样你就可以简单地使用try/catch来处理你的错误,并在parallel函数内部返回部分结果

谢谢,好问题!当我在 Mac 前时,我会更新我的答案。
2021-04-20 22:50:13
这是一个不好的做法。切勿对两个或多个异步并行任务使用多个等待,因为您将无法认真处理错误。它仅适用于积极的情况,但在消极的情况下(异步任务拒绝),尽管您使用 try/catch,但您将始终以未处理的错误结束。您必须始终将 Promise.all 用于异步并行任务。在此处查看我的答案:stackoverflow.com/a/54291660/3826175如果您需要全局和单独处理错误,请使用以下命令:try { let [val1, val2] = await Promise.all([ task1().catch(e => ...), task2().catch(e => ...) ]); } catch(e) { }
2021-04-23 22:50:13
请注意,要在 Node.js 中运行此示例,您需要使用(当前处于实验阶段的)Performance Timing API例如..将其添加到示例的顶部const { performance } = require('perf_hooks');
2021-04-25 22:50:13
双方seriesparallel有一个“快速失败”的行为了。答案文本具有误导性。
2021-05-03 22:50:13
我对您的代码有一些疑问。你怎么能“组合”一个等待并行池。我的意思是在你的例子中,你知道你有两个任务要执行和返回结果。例如,我如何使用 for 循环进行组合(假设我不知道编写脚本时的任务数)。我的用例:我从 HTTP 调用中检索一些 id,然后为每个 id 运行一个任务。如何并行运行检索到的 id 的所有任务?
2021-05-09 22:50:13

如果您对 Promise.all 的快速失败行为和解构赋值语法没有意见:

const [userResponse, postsResponse] = await Promise.all([
  fetchUserAsync(),
  fetchPostsAsync(),
]);
Chrome?page=home:36 Uncaught SyntaxError: await is only valid in async function在尝试这种方法时给了我.. 但可能是错误的用例
2021-04-29 22:50:13
@Mayhem 您需要将该代码包装在异步函数中
2021-05-13 22:50:13

对于那些询问如何将其扩展到运行时确定的调用次数的人,您可以使用 2 个循环。第一个启动所有任务,第二个等待一切完成

console.clear();

function wait(ms, data) {
  return new Promise( resolve => setTimeout(resolve.bind(this, data), ms) );
}

/** 
 * While here we call the functions first,
 * then wait for the result later, so 
 * this will finish in 500ms.
 */
async function runTasks(timings) {
  let tasks = [];
  for (let i in timings) {
      tasks.push(wait(timings[i], `Result of task ${i}`));
  }

  /* Want fast fail? use Promise.All */
  //return Promise.All(tasks);
  
  let results = [];
  for (let task of tasks) {
       results.push(await task);
  }

  return results;
}

async function taskRunner(fn, arg, label) {
  const startTime = performance.now();
  console.log(`Task ${label} starting...`);
  let result = await fn(arg);
  console.log(`Task ${label} finished in ${ Number.parseInt(performance.now() - startTime) } miliseconds with,`, result);
}

void taskRunner(runTasks, [50,100,200,60,500], 'Task List');

我实际上只是做了同样的事情。通过使用 promises 然后使用 Promise.all 在最后同步它们,您可以执行许多并发请求,但请确保在完成之前返回所有结果。

在最后一个例子中看到这里:http : //javascriptrambling.blogspot.com/2017/04/to-promised-land-with-asyncawait-and.html

啊,没想到你问了自己的问题,所以你可以回答它。但是在最初的问题中,您没有提到要处理每个错误。通常只处理一个错误就足够了,因为即使一个部分失败,一个过程也很少能成功。除非需要,否则代码越少越好。
2021-04-17 22:50:13
正如我所说, Promise.all 具有快速失败行为,因此您不会归档相同的目标。
2021-05-15 22:50:13

伪代码可以写成如下:

fn async task() {
  result-1 = doAsync();
  result-2 = doAsync();
  result-n = doLongAsync();
  try{
  // handle results together
  combinedResult = handleResults(await result-1, await result-2);
  lastResult = handleLastResult(await result-n);
  }
  catch(err){
   console.error(err)
  }

}

result-1、result-2、result-n 将并行运行。combineResult 和 lastResult 也将并行运行。但是,一旦 result-1 和 result-2 可用,将返回 combineResult 值,即 handleResults 函数的返回,而一旦 result-n 可用,将返回 lastResult 值,即 handleLastResult。

希望这可以帮助

您可以参考此链接进一步了解:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
2021-04-21 22:50:13