Привет и добро пожаловать! 👋

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

Что такое асинхронный/ожидающий?

Прежде чем мы углубимся в объяснение разницы, давайте сначала поговорим об async/await.

Async/await — мощная функция, упрощающая асинхронное программирование в JavaScript. Он обеспечивает более читаемый и синхронный синтаксис для работы с промисами и обработки асинхронных операций.

Ключевое слово async используется для объявления функции как асинхронной. Асинхронная функция неявно возвращает обещание, что позволяет нам использовать в нем await. Ключевое слово await можно применить к обещанию, приостанавливая выполнение функции до тех пор, пока обещание не будет разрешено или отклонено. Это помогает избежать ада обратных вызовов и написать код, который выполняется синхронно, даже если он имеет дело с асинхронными операциями.

async function getUserById(id) {
  // Getting data
}

async function getUser(req, res) {
  let result  = await getUserById(req.params['id']);
  // Continue after getting data
}

Использование async/await с циклом for

Цикл for — это традиционная конструкция цикла в JavaScript, используемая для перебора массива или других итерируемых объектов.

При использовании async/await с циклом for каждая итерация ожидает разрешения предыдущей асинхронной операции, прежде чем перейти к следующей итерации. Это сохранит операции цикла в нужном порядке и не преподнесет нам никаких сюрпризов.

Представьте, что у нас есть асинхронная функция с именем getData, которая бездействует в течение заданного времени, а затем регистрирует сообщение DONE с индексом, который использовался для ее вызова.

async function getData(i, time) {
  await sleep(time);
  console.log(`DONE ${i}`);
}

async function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Мы вызовем эту функцию внутри цикла for, а затем посмотрим на результат:

async function main() {
  let items = [0, 1, 2, 3];
  for(let i=0; i < items.length; i++) {
    await getData(items[i], 1000 * (5 - items[i]));
  }
  console.log("END OF MAIN");
}
main().then();

Как вы видите, первый элемент должен выполняться дольше, а последний элемент займет меньше времени, потому что мы пропускаем время 1000 * (5 - items[i]), поэтому вывод будет таким:

DONE 0
DONE 1
DONE 2
DONE 3
END OF MAIN

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

Использование async/await с циклом forEach

Объяснение: Цикл forEach — это встроенный в JavaScript метод работы с массивами, который перебирает каждый элемент массива.

let items = ['a', 'b', 'c'];
items.forEach(item => {
  console.log(item);
});

// Output: 
a
b
c

Хотя он обеспечивает краткий способ перебора массива, использование async/await в цикле forEach может иметь некоторые тонкие отличия по сравнению с использованием его с циклом for.

При использовании await в цикле forEach каждая итерация одновременно инициирует асинхронную операцию, не дожидаясь завершения предыдущей итерации. Это означает, что порядок выполнения асинхронных операций не гарантируется, что может привести к неупорядоченным результатам.

Мы выполним ту же функцию getData, но на этот раз с циклом forEach:

async function main() {
  let items = [0, 1, 2, 3];
  items.forEach(async item => {
    await getData(item, 1000 * (5 - item));
  });
  console.log("END OF MAIN");
}

Поскольку все итерации инициированы и выполнение последнего элемента занимает меньше времени, это будет вывод:

END OF MAIN
DONE 3
DONE 2
DONE 1
DONE 0

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

Рекомендации и варианты использования

При выборе между использованием async/await в цикле for или цикле forEach следует помнить о некоторых важных соображениях.

Использование async/await с циклом for подходит, когда:

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

С другой стороны, использование async/await с циклом forEach может быть более подходящим, когда:

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

Важно отметить, что поведение async/await с циклом forEach может удивить из-за отсутствия упорядоченного выполнения. Если требуется сохранение порядка или обработка зависимостей между итерациями, лучше использовать цикл for.

В сценариях, где неупорядоченное выполнение допустимо или желательно, альтернативы, такие как использование Promise.all или использование библиотек, таких как async или Bluebird, могут обеспечить больший контроль и гибкость при работе с асинхронными операциями в цикле forEach.

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

Это была одна из обязательных тем в JavaScript, и в следующих блогах будет больше. А пока продолжайте программировать и следите за обновлениями🌟.