Вызовите функции aync/await параллельно

Насколько я понимаю, в ES7/ES2016 добавление нескольких await в код будет схоже с цепочкой .then() с promises, что означает, что они будут выполняться один за другим, а не в parallerl. Так, например, у нас есть этот код:

await someCall();
await anotherCall();

Я правильно понял, что anotherCall() будет вызван только тогда, когда someCall() будет завершен? Каков самый элегантный способ их параллельного вызова?

Я хочу использовать его в Node, так что, может быть, есть решение с асинхронной библиотекой?

EDIT: меня не устраивает решение, предоставленное в этом вопросе: Замедление из-за непараллельного ожидания promises в асинхронных генераторах, поскольку оно использует генераторы, и я спрашиваю о более общем использовании.

Ответ 1

Вы можете ждать на Promise.all():

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

Для сохранения результатов:

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

Ответ 2

TL; DR

Используйте Promise.all для параллельных вызовов функций, поведение ответов не правильно при возникновении ошибки.


Сначала выполните все асинхронные вызовы одновременно и получите все объекты Promise. Во-вторых, используйте await на объектах Promise. Таким образом, пока вы ожидаете, пока первое 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: http://jsbin.com/xerifanima/edit?js,console

Предостережение: не имеет значения, находятся ли await вызовы на одной и той же линии или на разных линиях, при условии, что первый await вызов происходит после всех асинхронных вызовов. Смотрите комментарий JohnnyHK.


Обновление: этот ответ имеет различную синхронизацию при обработке ошибок в соответствии с ответом @bergi, он НЕ выдает ошибку при возникновении ошибки, но после выполнения всех обещаний. Я сравниваю результат с @jonny tip: [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();
})();

Ответ 3

Обновить:

Первоначальный ответ затрудняет (а в некоторых случаях невозможен) правильную обработку отклонений обещаний. Правильным решением является использование Promise.all:

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

Оригинальный ответ:

Просто убедитесь, что вы вызываете обе функции, прежде чем вы ждете одну из них:

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

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

Ответ 4

Есть еще один способ без Promise.all() сделать это параллельно:

Во-первых, у нас есть 2 функции для печати чисел:

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

Это последовательно:

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

Это параллельно:

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

Ответ 5

Я создал суть, тестирующую несколько разных способов выполнения обещаний, с результатами. Может быть полезно увидеть варианты, которые работают.

Ответ 6

Интуитивное решение

function wait(ms, data) {
  console.log('Starting task:', data, ms);
  return new Promise( resolve => setTimeout(resolve, ms, data) );
}

(async function parallel() {

  // step 1 - initiate all promises
  console.log('STARTING')
  let task1 = wait(2000, 'parallelTask1') // PS: see Exception handling below
  let task2 = wait(500, 'parallelTask2')
  let task3 = wait(1000, 'parallelTask3')

  // step 2 - await all promises
  console.log('WAITING')
  task1 = await task1
  task2 = await task2
  task3 = await task3

  // step 3 - all results are 100% ready
  console.log('FINISHED')
  console.log('Result:', task1, task2, task3)

})()

Ответ 7

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

Хотя установка p1, p2 и p3 не выполняет их строго параллельно, они не задерживают выполнение, и вы можете перехватывать контекстные ошибки с помощью перехвата.

Ответ 8

В моем случае у меня есть несколько задач, которые я хочу выполнять параллельно, но мне нужно сделать что-то другое с результатом этих задач.

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

И вывод:

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

Ответ 9

Я голосую за:

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

Помните о том, как вы вызываете функции, это может вызвать неожиданный результат:

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

Но после этого всегда запускается запрос на создание нового пользователя

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

Ответ 10

Я создаю вспомогательную функцию waitAll, может быть, она может сделать ее слаще. Пока он работает только в nodejs, а не в браузере Chrome.

    //const parallel = async (...items) => {
    const waitAll = async (...items) => {
        //this function does start execution the functions
        //the execution has been started before running this code here
        //instead it collects of the result of execution of the functions

        const temp = [];
        for (const item of items) {
            //this is not
            //temp.push(await item())
            //it does wait for the result in series (not in parallel), but
            //it doesn't affect the parallel execution of those functions
            //because they haven started earlier
            temp.push(await item);
        }
        return temp;
    };

    //the async functions are executed in parallel before passed
    //in the waitAll function

    //const finalResult = await waitAll(someResult(), anotherResult());
    //const finalResult = await parallel(someResult(), anotherResult());
    //or
    const [result1, result2] = await waitAll(someResult(), anotherResult());
    //const [result1, result2] = await parallel(someResult(), anotherResult());