Как запустить async/ожидание параллельно в Javascript

Наконец async/await будет поддерживается во всем крупном браузере в ближайшее время, кроме IE. Итак, теперь мы можем начать писать более читаемый код с помощью async/await, но есть улов. Многие люди используют async ждут вот так:

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

Ответ 1

Вы можете написать примерно следующее:

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

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

Это легко? Но есть улов. Promise.all имеет отказоустойчивое поведение, что означает, что он отклонит, как только один из promises отклонен. Вероятно, вы хотите получить более надежное решение, в котором мы отвечаем за обработку отказов любого из наборов. К счастью, существует решение, оно может быть достигнуто просто с помощью 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 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" }
 */

Ответ 2

Если вы в порядке с отказоустойчивым поведением Promise.all и синтаксисом назначения деструктуризации:

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

Ответ 3

Я просто сделал то же самое. Используя promises, а затем Promise.all, чтобы синхронизировать их в конце, вы можете выполнять много одновременных запросов, но затем убедитесь, что у вас есть все результаты до того, как вы закончите.

См. здесь в последнем примере: http://javascriptrambling.blogspot.com/2017/04/to-promised-land-with-asyncawait-and.html

Ответ 4

Псевдокод может быть записан следующим образом:

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

}

Результат-1, результат-2, результат-n будет выполняться параллельно. togetherResult и lastResult также будут выполняться параллельно. Однако значение integerResult, то есть возврат функции handleResults будет возвращено после получения результата 1 и результата-2 и значения lastResult i.e handleLastResult будет возвращен после получения результата-n.

Надеюсь, что это поможет

Ответ 5

Во-первых, ваш код является блокирующим кодом?

Если да, помните, что javascript - это один поток, поэтому вы не можете запускать два синхронных кода, например, два цикла (одновременно или в течение).

Но можно достичь этого с помощью веб-рабочих, мне удалось выполнить функции в общих веб-рабочих и без использования отдельных 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;
    }   
}