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

时间:2017-02-10 11:58:06

标签: javascript async-await

除IE之外,所有主流浏览器中的async / await最终都会supported。 所以现在我们可以开始使用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);
}

7 个答案:

答案 0 :(得分:97)

你可以这样写:

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

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

这很容易吗?但是有一个问题! 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" }
 */

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

这样您只需使用try / catch来处理错误,并在parallel函数中返回部分结果。

答案 1 :(得分:18)

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

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

答案 2 :(得分:2)

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




答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

伪代码可以写成如下:

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将并行运行。 combinedResult和lastResult也将并行运行。 但是,一旦result-1和result-2可用,将返回combinedResult值,即handleResults函数的返回,并且一旦result-n可用,将返回lastResult值,即handleLastResult。

希望这有帮助

答案 5 :(得分:0)

首先,您的代码是否为阻止代码?

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

但是,有可能使用Web Workers实现,我设法在通用Web worker中执行函数,而不使用单独的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;
    }   
}

答案 6 :(得分:0)

选定的答案提出了两种等待所有“衍生”异步函数终止的方法。

我的建议是使用 setImmediate(nodejs ~相当于 setTimeout(0))生成每个异步函数来运行每个函数并在所有函数完成之前获得中间结果:

for (let i = 0; i < numSpawns; i++ ) {

  // nodejs
  setImmediate( async () => { console.log( await runAsyncFunction(msecsMax) ) } )

  // browser
  // substitute setImmediate with setTimeout( await runAsyncFunction, 0, msecsmax )

}  

完整的演示代码

/**
 * parallel.js
 * demo, to "spawn" in parallel multiple async functions
 */

/**
 * sleep
 * warp setTimeout, returning a value
 *
 * @async
 * @param {Number}  msecs number of milliseconds
 * @return {Number} msecs
 */
function sleep(msecs) {
  return new Promise(function(resolve /*, reject*/) {
     setTimeout( () => { resolve(msecs) }, msecs )
   })
}

/**
 * randomInteger
 * Returns a random integer number between min (inclusive) and max (inclusive)
 * @param {Number}  min
 * @return {Number} max
 */
function randomInteger(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}


/**
 * runAsyncFunction
 * simulate an async function, 
 * returning, after a random number of msecs, the number of msecs
 *
 * @async
 * @param {Number}  msecsMax max duration in milliseconds
 * @return {Number} random number of msecs
 */
async function runAsyncFunction(msecsMax) {
  const msecsMin = 500
  return await sleep( randomInteger(msecsMin, msecsMax) )
}



async function parallel(numSpawns, msecsMax) {
  for (let i = 0; i < numSpawns; i++ ) {

    // nodejs
    setImmediate( async () => { console.log( await runAsyncFunction(msecsMax) ) } )

    // browser
    // substitute setImmediate with setTimeout( await runAsyncFunction, 0, msecsmax )
  
  }  
}  


async function main() {

  const msecsMax = 3000
  const numSpawns = 10
  
  // runs in "parallel" 10 async functions, 
  // each one returning after a sleep of a random number of milliseconds (between 500 to 3000)  
  parallel(numSpawns, msecsMax)
}

main()

运行程序:

$ /usr/bin/time --verbose node parallel
1204
1869
1983
2042
2119
2220
2222
2611
2642
2660
    Command being timed: "node parallel"
    User time (seconds): 0.07
    System time (seconds): 0.00
    Percent of CPU this job got: 3%
    Elapsed (wall clock) time (h:mm:ss or m:ss): 0:02.72
    Average shared text size (kbytes): 0
    Average unshared data size (kbytes): 0
    Average stack size (kbytes): 0
    Average total size (kbytes): 0
    Maximum resident set size (kbytes): 31568
    Average resident set size (kbytes): 0
    Major (requiring I/O) page faults: 0
    Minor (reclaiming a frame) page faults: 2160
    Voluntary context switches: 39
    Involuntary context switches: 1
    Swaps: 0
    File system inputs: 0
    File system outputs: 0
    Socket messages sent: 0
    Socket messages received: 0
    Signals delivered: 0
    Page size (bytes): 4096
    Exit status: 0