Ожидание более чем одной одновременной операции ожидания

Как я могу изменить следующий код так, чтобы выполнялись обе операции асинхронного вызова и давалась возможность запуска одновременно?

const value1 = await getValue1Async();
const value2 = await getValue2Async();
// use both values

Нужно ли мне что-то делать?

const p1 = getValue1Async();
const p2 = getValue2Async();
const value1 = await p1;
const value2 = await p2;
// use both values

Ответы

Ответ 1

TL; DR

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

const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);

Хотя ваше решение выполняет две операции параллельно, оно не обрабатывает отклонение должным образом, если оба обещания отклоняются.

Подробности:

Ваше решение запускает их параллельно, но всегда ждет завершения первого, а затем ожидания второго. Если вы просто хотите запустить их, запустить их параллельно и получить оба результата, это нормально. (Нет, это не так, продолжайте читать...) Обратите внимание, что если первый берет (скажем, ) пять секунд, чтобы завершить работу, а вторая потерпит неудачу за одну секунду, ваш код будет ждать полные пять секунд, прежде чем произойдет сбой.

К сожалению, в настоящее время нет синтаксиса await для параллельного ожидания, поэтому у вас есть неловкость, которую вы перечислили, или Promise.all. (Хотя обсуждалось await.all или подобное, может быть, когда-нибудь.)

Версия Promise.all:

const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);

... что более кратко, а также не ожидает завершения первой операции, если вторая не удалась быстро (например, в моем примере пять секунд/одна секунда выше, вышеприведенное будет отклонено в одну секунду, а не в ожидании 5). Также обратите внимание, что с вашим исходным кодом, если второе обещание отклоняется до разрешения первого обещания, вы вполне можете получить ошибку "необработанное отклонение" в консоли (в настоящее время вы используете Chrome v61; обновление: более новое версии имеют более интересное поведение), хотя эта ошибка, возможно, ложная (поскольку вы, в конечном счете, обрабатываете отклонение, поскольку этот код явно находится в функции async), и поэтому функция будет перехватывать отклонение и сделать свое обещание отклонить вместе с ним) (обновление: еще раз, изменено). Но если оба обещания отклоняются, вы получите настоящую необработанную ошибку отклонения, потому что поток управления никогда не достигает const value2 = await p2; и, таким образом, отклонение p2 никогда не обрабатывается.

Необработанные отклонения - это плохая вещь ™ (настолько, что скоро Node.js прервет процесс на действительно необработанных отклонениях, точно так же, как необработанные исключения &— потому что это то, что они есть), поэтому лучше избегать "получите обещание тогда await это "шаблон в вашем вопросе.

Вот пример разницы во времени в случае сбоя (с использованием 500 мс и 100 мс, а не 5 секунд и 1 секунда) и, возможно, также, возможно, ложная необработанная ошибка отклонения (откройте настоящую консоль браузера, чтобы увидеть он):

const getValue1Async = () => {
  return new Promise(resolve => {
    setTimeout(resolve, 500, "value1");
  });
};
const getValue2Async = () => {
  return new Promise((resolve, reject) => {
    setTimeout(reject, 100, "error");
  });
};

// This waits the full 500ms before failing, because it waits
// on p1, then on p2
(async () => {
  try {
    console.time("separate");
    const p1 = getValue1Async();
    const p2 = getValue2Async();
    const value1 = await p1;
    const value2 = await p2;
  } catch (e) {
    console.error(e);
  }
  console.timeEnd("separate");
})();

// This fails after just 100ms, because it does not wait for p1
// to finish first, it rejects as soon as p2 rejects
setTimeout(async () => {
  try {
    console.time("Promise.all");
    const [value1, value2] = await Promise.all([getValue1Async(), getValue2Async()]);
  } catch (e) {
    console.timeEnd("Promise.all", e);
  }
}, 1000);
Open the real browser console to see the unhandled rejection error.

Ответ 2

Я думаю, что это должно работать:

 const [value1, value2] = await Promise.all([getValue1Async(),getValue2Async()]);

Ниже приведен более подробный пример, если он помогает понять:

const promise1 = async() => {
  return 3;
}

const promise2 = async() => {
  return 42;
}

const promise3 = async() => {
  return 500;
  // emulate an error
  // throw "something went wrong...";
}

const f1 = async() => {

  try {
    // returns an array of values
    const results = await Promise.all([promise1(), promise2(), promise3()]);
    console.log(results);
    console.log(results[0]);
    console.log(results[1]);
    console.log(results[2]);

    // assigns values to individual variables through 'array destructuring'
    const [value1, value2, value3] = await Promise.all([promise1(), promise2(), promise3()]);

    console.log(value1);
    console.log(value2);
    console.log(value3);

  } catch (err) {
    console.log("there was an error: " + err);
  }

}

f1();

Ответ 3

Используйте .catch() и Promise.all()

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

В приведенном ниже примере возвращается массив [[error, results],...], чтобы упростить обработку результатов и/или ошибок.

let myTimeout = (ms, is_ok) =>
  new Promise((resolve, reject) => 
    setTimeout(_=> is_ok ? 
                   resolve('ok in ${ms}') :
                   reject('error in ${ms}'),
               ms));

let handleRejection = promise => promise
  .then((...r) => [null, ...r])
  .catch(e => [e]); 

(async _=> {
  let res = await Promise.all([
    myTimeout(100, true),
    myTimeout(200, false),
    myTimeout(300, true),
    myTimeout(400, false)
  ].map(handleRejection));
  console.log(res);
})();

Ответ 4

Решает вместо обещаний

const wait = (ms, data) => new Promise( resolve => setTimeout(resolve, ms, data) )
const reject = (ms, data) => new Promise( (r, reject) => setTimeout(reject, ms, data) )
const e = e => 'err:' + e
const l = l => (console.log(l), l)

;(async function parallel() {

  let task1 = reject(500, 'parallelTask1').catch(e).then(l)
  let task2 = wait(2500, 'parallelTask2').catch(e).then(l)
  let task3 = reject(1500, 'parallelTask3').catch(e).then(l)

  console.log('WAITING')

  ;[task1, task2, task3] = [await task1, await task2,  await task3]

  console.log('FINISHED', task1, task2, task3)

})()