Javascript 并行调用 async/await 函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/35612428/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 17:59:22  来源:igfitidea点击:

Call async/await functions in parallel

javascriptnode.jsasynchronousecmascript-6babeljs

提问by Victor Marchuk

As far as I understand, in ES7/ES2016 putting multiple await's in code will work similar to chaining .then()with promises, meaning that they will execute one after the other rather than in parallerl. So, for example, we have this code:

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

await someCall();
await anotherCall();

Do I understand it correctly that anotherCall()will be called only when someCall()is completed? What is the most elegant way of calling them in parallel?

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

I want to use it in Node, so maybe there's a solution with async library?

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

EDIT: I'm not satisfied with the solution provided in this question: Slowdown due to non-parallel awaiting of promises in async generators, because it uses generators and I'm asking about a more general use case.

编辑:我对这个问题中提供的解决方案不满意:Slowdown due to non-parallel awaits in async generators,因为它使用生成器,我问的是一个更一般的用例。

回答by madox2

You can await on Promise.all():

您可以等待Promise.all()

await Promise.all([someCall(), anotherCall()]);

To store the results:

存储结果:

let [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);

Note that Promise.allfails fast, which means that as soon as one of the promises supplied to it rejects, then the entire thing rejects.

请注意,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'

If, instead, you want to wait for all the promises to either fulfill or reject, then you can use Promise.allSettled. Note that Internet Explorer does not natively support this method.

相反,如果您想等待所有承诺履行或拒绝,那么您可以使用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" }]

回答by Haven

TL;DR

TL; 博士

Use Promise.allfor the parallel function calls, the answer behaviors not correctly when the error occurs.

使用Promise.all的并行函数调用,答案的行为不正确时出现错误。



First, execute allthe asynchronous calls at once and obtain all the Promiseobjects. Second, use awaiton the Promiseobjects. This way, while you wait for the first Promiseto resolve the other asynchronous calls are still progressing. Overall, you will only wait for as long as the slowest asynchronous call. For example:

首先,一次执行所有异步调用并获取所有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 example: http://jsbin.com/xerifanima/edit?js,console

JSbin 示例:http://jsbin.com/xerifanima/edit?js,console

Caveat:It doesn't matter if the awaitcalls are on the same line or on different lines, so long as the first awaitcall happens afterall of the asynchronous calls. See JohnnyHK's comment.

警告:await调用是在同一行还是在不同的行上都没有关系,只要第一个await调用发生所有异步调用之后即可。请参阅 JohnnyHK 的评论。



Update:this answer has a different timing in error handling according to the @bergi's answer, it does NOTthrow out the error as the error occurs but after all the promises are executed. I compare the result with @jonny's tip: [result1, result2] = Promise.all([async1(), async2()]), check the following code snippet

更新:这个回答有错误不同时间处理根据@ 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();
})();

回答by Jonathan Potter

Update:

更新:

The original answer makes it difficult (and in some cases impossible) to correctly handle promise rejections. The correct solution is to use Promise.all:

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

const [someResult, anotherResult] = await Promise.all([someCall(), anotherCall()]);

Original answer:

原答案:

Just make sure you call both functions before you await either one:

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

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

// Await both promises    
const someResult = await somePromise;
const anotherResult = await anotherPromise;

回答by user2883596

There is another way without Promise.all() to do it in parallel:

还有另一种没有 Promise.all() 的方法可以并行执行:

First, we have 2 functions to print numbers:

首先,我们有两个函数来打印数字:

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

This is sequential:

这是顺序的:

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

This is parallel:

这是平行的:

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

回答by Jonathan Sudiaman

This can be accomplished with Promise.allSettled(), which is similar to Promise.all()but without the fail-fast behavior.

这可以通过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!"}

Note: This is a bleeding edge feature with limited browser support, so I stronglyrecommend including a polyfill for this function.

注意:这是一项具有有限浏览器支持的前沿功能,因此我强烈建议为此功能包含一个 polyfill。

回答by SkarXa

I've created a gisttesting some different ways of resolving promises, with results. It may be helpful to see the options that work.

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

回答by Thrunobulax

    // 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
    */

Whilst setting p1, p2 and p3 is not strictly running them in parallel, they do not hold up any execution and you can trap contextual errors with a catch.

虽然设置 p1、p2 和 p3 并不是严格并行运行它们,但它们不会阻止任何执行,您可以使用 catch 捕获上下文错误。

回答by Alex Dresko

In my case, I have several tasks I want to execute in parallel, but I need to do something different with the result of those tasks.

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

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

And the output:

和输出:

Starting task: moose 1000
Starting task: taco 500
Starting task: burp 5000
taco
moose
burp
done

回答by Thiago Conrado

await Promise.all([someCall(), anotherCall()]); as already mention will act as a thread fence (very common in parallel code as CUDA), hence it will allow all the promises in it to run without blocking each other, but will prevent the execution to continue until ALL are resolved.

等待 Promise.all([someCall(), anotherCall()]); 如前所述,它将充当线程栅栏(在作为 CUDA 的并行代码中非常常见),因此它将允许其中的所有 promise 运行而不会相互阻塞,但会阻止执行继续,直到解决所有问题。

another approach that is worth to share is the Node.js async that will also allow you to easily control the amount of concurrency that is usually desirable if the task is directly linked to the use of limited resources as API call, I/O operations, etc.

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

Credits to the Medium article autor (read more)

归功于 Medium 文章作者(阅读更多

回答by Hoang Le Anh Tu

I vote for:

我投票赞成:

await Promise.all([someCall(), anotherCall()]);

Be aware of the moment you call functions, it may cause unexpected result:

注意调用函数的那一刻,可能会导致意想不到的结果:

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

But following always triggers request to create new User

但是以下总是触发创建新用户的请求

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