В JavaScript синтаксис async и await отлично подходит для выполнения нескольких обещаний.

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

Когда мы должны использовать Async и Await?

async и await следует использовать только тогда, когда нам нужно выполнять обещания, которые выполняются последовательно.

В противном случае мы не должны их использовать. Например, в следующем примере второе обещание зависит от результатов первого обещания, поэтому они должны выполняться последовательно:

(async () => {
  const breedsRes = await fetch('https://dog.ceo/api/breeds/list/all');
  const {
    message
  } = await breedsRes.json();
  const breed = Object.keys(message)[0];
  const imgRes = await fetch(`https://dog.ceo/api/breed/${breed}/images/random`)
  const img = await imgRes.json();
  console.log(img.message);
})();

В приведенном выше коде первый запрос возвращает породы из Dog API, а затем результат используется во втором обещании Dog API для получения изображения.

В этом случае имеет смысл использовать async и await, поскольку нам нужно дождаться завершения первого обещания, прежде чем мы перейдем ко второму.

Цикл for...await...of - это просто async и await в форме цикла. Следовательно, он также последовательно выполняет обещания в повторяемом объекте.

Они делают то же самое, но цикл for..await...of позволяет нам последовательно запускать обещания в цикле, чего мы не могли бы сделать раньше без внешней библиотеки.

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

Несвязанные обещания не должны использовать асинхронно и ждать последовательно

С другой стороны, если мы выполняем несвязанные обещания, нам не следует использовать async и await, поскольку мы без нужды ждем завершения предыдущего обещания, прежде чем запускать следующее.

Например, следующий код запускает 2 несвязанных обещания в одной async функции:

(async () => {
  const res = await fetch('https://dog.ceo/api/breeds/image/random')
  const {
    message
  } = await res.json()
  console.log(message);
const useRes = await fetch('https://randomuser.me/api/')
  const {
    results
  } = await useRes.json()
  console.log(results);
})();

Первый получает данные из Dog API, а второй - случайно сгенерированные пользовательские данные.

Они не зависят друг от друга, поэтому им не нужно использовать async и await последовательно.

Два запроса вместе занимают 510 мс для выполнения через обычное широкополосное соединение и 4 секунды для завершения через медленное соединение 3G.

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

Мы можем сделать это с Promise.all следующим образом:

(async () => {
  const [res, userRes] = await Promise.all([
    fetch('https://dog.ceo/api/breeds/image/random'),
    fetch('https://randomuser.me/api/')
  ])
  const [{
    message
  }, results] = await Promise.all([res.json(), userRes.json()]);
  console.log(message, results);
})();

Приведенный выше код занимает 360 мс для запуска через обычное широкополосное соединение, а при использовании медленного 3G-соединения для запуска требуется 3 секунды.

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

Следовательно, мы никогда не должны использовать async и await последовательно с несвязанными обещаниями.

Вместо этого мы должны использовать async и await с Promise.all, а затем поместить наши обещания в массив, который мы передали.

Мы сгруппировали fetch обещания в один массив и передали его в Promise.all, затем мы делаем то же самое с вызовом json после выполнения fetch обещаний.

Это потому, что json обещания зависят от fetch обещаний, но fetch обещания не зависят друг от друга, а json обещания также независимы.

Теперь, когда мы используем Promise.all, наш код работает намного быстрее, имея те же преимущества от использования async и await.

Заключение

Мы можем использовать async и await для объединения обещаний, которые зависят от другого.

Точно так же цикл for...await...of также последовательно выполняет обещания в повторяемом объекте. Следовательно, он ведет себя так же, как async и await.

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

Для этого мы должны использовать Promise.all с async и await для параллельного выполнения несвязанных обещаний.

Таким образом, нам не нужно ждать, пока одно несвязанное обещание будет выполняться за другим и вызывать ненужное ожидание.