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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/42158853/
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 00:57:12  来源:igfitidea点击:

How run async / await in parallel in Javascript

javascriptasync-await

提问by NoNameProvided

Finally async/awaitwill be supportedin all major browser soon except IE. So now we can start writing more readable code with async/awaitbut there is a catch. A lot of people use async await like this:

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

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

While this code is readable it has a problem, it runs the functions in series, it won't start fetching posts until the fetching of the user is finished. The solutions is simple, we need to fetch the resources in parallel.

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

So what I want to do is (in pseudo language):

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

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

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

  lastResult = handleLastResult(result-n);
}

回答by NoNameProvided

You can write something like this:

你可以这样写:

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

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

This is easy right? But there is a catch. Promise.allhas fail-fast behaviourwhich means, it will reject as soon as one of the promises rejected. Probably you want a more robust solution where we are in charge of handling the rejections any of the fetches. Luckily there is a solution, it can be achieved simply with async/awaitwithout the need of using Promise.all. A working example:

这很容易吧?但是有一个问题!Promise.all具有快速失败行为,这意味着,一旦其中一个承诺被拒绝,它就会拒绝。可能您想要一个更强大的解决方案,我们负责处理任何提取的拒绝。幸运的是,有一个解决方案,只需使用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" }
 */

Note: You will need a browser which has async/awaitenabledto run this snippet (or nodejs v7 and above)

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

This way you can use simply try/ catchto handle your errors, and return partial results inside the parallelfunction.

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

回答by ricka

If you're ok with the fail-fast behavior of Promise.all and the destructuring assignment syntax:

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

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

回答by Wilco

For those asking how you extend this to a run-time determined number of calls, you can use 2 loops. The first starts all the tasks, the second waits for everything to finish

对于那些询问如何将其扩展到运行时确定的调用次数的人,您可以使用 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');

回答by Nagaraja Malla

The pseudo code can be written as below:

伪代码可以写成如下:

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 will run in parallel. combinedResult and lastResult will also run in parallel. However combinedResult value i.e. return of handleResults function will be returned once the result-1 and result-2 are available and lastResult value i.e handleLastResult will be returned once the result-n is available.

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

Hope this helps

希望这可以帮助

回答by Fernando Carvajal

First, are your code a blocking-code?

首先,您的代码是阻塞代码吗?

If yes, remember that javascript is single thread, so you cannot run two synchronous codes, for example two loops (for or while) at the same time.

如果是,请记住 javascript 是单线程的,因此您不能同时运行两个同步代码,例如两个循环(for 或 while)。

But, it is possible to achieve that using Web Workers, I managed to execute functions in generic web workers and without using separated js files.

但是,可以使用 Web Workers 实现这一点,我设法在通用Web Workers中执行功能,而不使用单独的 js 文件。

setInterval(()=>{console.log("non blocked " + Math.random())}, 900)

console.log("start blocking code in parallel in web Worker")
console.time("blocked")

genericWorker(window, ["blockCpu", function (block){    
    block(10000) //This blockCpu function is defined below
    return "\n\nbla bla\n" //This is catched in the resolved promise

}]).then(function (result){
    console.timeEnd("blocked")
    console.log("End of blocking code", result)
})
.catch(function(error) { console.log(error) })


/*  A Web Worker that does not use a File, it create that from a Blob
    @cb_context, The context where the callback functions arguments are, ex: window
    @cb, ["fn_name1", "fn_name2", function (fn1, fn2) {}]
        The callback will be executed, and you can pass other functions to that cb
*/
function genericWorker(cb_context, cb) {
    return new Promise(function (resolve, reject) {

        if (!cb || !Array.isArray(cb))
            return reject("Invalid data")

        var callback = cb.pop()
        var functions = cb

        if (typeof callback != "function" || functions.some((fn)=>{return typeof cb_context[fn] != "function"}))
            return reject(`The callback or some of the parameters: (${functions.toString()}) are not functions`)

        if (functions.length>0 && !cb_context)
            return reject("context is undefined")

        callback = fn_string(callback) //Callback to be executed
        functions = functions.map((fn_name)=> { return fn_string( cb_context[fn_name] ) })

        var worker_file = window.URL.createObjectURL( new Blob(["self.addEventListener('message', function(e) { var bb = {}; var args = []; for (fn of e.data.functions) { bb[fn.name] = new Function(fn.args, fn.body); args.push(fn.name)}; var callback = new Function( e.data.callback.args, e.data.callback.body); args = args.map(function(fn_name) { return bb[fn_name] });  var result = callback.apply(null, args) ;self.postMessage( result );}, false)"]) )
        var worker = new Worker(worker_file)

        worker.postMessage({ callback: callback, functions: functions })

        worker.addEventListener('error', function(error){ return reject(error.message) })

        worker.addEventListener('message', function(e) {
            resolve(e.data), worker.terminate()
        }, false)

        //From function to string, with its name, arguments and its body
        function fn_string (fn) {
            var name = fn.name, fn = fn.toString()

            return { name: name, 
                args: fn.substring(fn.indexOf("(") + 1, fn.indexOf(")")),
                body: fn.substring(fn.indexOf("{") + 1, fn.lastIndexOf("}"))
            }
        }
    })
}

//random blocking function
function blockCpu(ms) {
    var now = new Date().getTime(), result = 0
    while(true) {
        result += Math.random() * Math.random();
        if (new Date().getTime() > now +ms)
            return;
    }   
}

回答by Kevin Williams

I actually just did this same thing. By using promises and then Promise.all to synchronize them at the end, you can do many concurrent requests, but then be sure you have all the results back before you finish.

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

See here in the last example: http://javascriptrambling.blogspot.com/2017/04/to-promised-land-with-asyncawait-and.html

请参见最后一个示例:http: //javascriptrambling.blogspot.com/2017/04/to-promised-land-with-asyncawait-and.html