Как использовать async/await с map в js

В какой-то момент вы, возможно, задавались вопросом, как использовать асинхронные функции в таких методах, как .map или .forEach, поскольку в этом небольшом блоге вы увидите наиболее распространенные ошибки и способы их решения.

Решение

Для этого в файле index.ts у нас будет следующий базовый код:

const usernames: string[] = ["jordanrjdev", "anonymous123", "channelyy"];

const simulateFetchData = (username: string): Promise<string> => {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve(`${username} is a valid username`);
    }, 1000);
  });
}

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


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

const dataUsers = usernames.map(async (username) => {
   return await simulateFetchData(username);
});
console.log(dataUsers);

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

[ Promise { <pending> }, Promise { <pending> }, Promise { <pending> } ]

Итак, чтобы решить эту проблему, у нас есть два варианта: использовать Promise.all или использовать for of.

For of

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

const getWithForOf = async() => {
   console.time("for of");
   const data = []
   for (const username of usernames) {
     let dataUser = await simulateFetchData(username);
     data.push(dataUser);
   }
   console.timeEnd("for of");
}
getWithForOf();

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


Имейте в виду, что если мы выполняем N итераций, и для решения каждой из них требуется 1 секунда, как в нашем примере, это означает, что в общей сложности потребуется 3 секунды, чтобы завершить выполнение этой части кода. Мы можем видеть это в выводе консоли благодаря console.time:

for of: 3,012s

Promise.all

Теперь мы будем использовать метод Promise.all для решения нашей проблемы, поэтому у нас будет следующий код:

const getWithPromiseAll = async() => {
   console.time("promise all");
   let data = await Promise.all(usernames.map(async (username) => {
     return await simulateFetchData(username);
   }))
   console.timeEnd("promise all");
}

getWithPromiseAll();

Как вы можете видеть, у нас есть метод Promise.all, который получает массив promises, помните, что он

 usernames.map(async (username) => {return await simulateFetchData(username);})

возвращает массив промисов, как раз то, что нам нужно, поэтому мы передаем его в Promise.all для их разрешения.


Этот метод приведет к параллельному разрешению всего асинхронного кода.


Итак, в отличие от for, давайте посмотрим, сколько времени потребуется для выполнения этой функции в консоли:

promise all: 980.3000000119209ms

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

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

Если у вас есть какие-либо вопросы или предложения, не забудьте оставить комментарий, до скорой встречи :)