자바스크립트에서 Array.map 함수에서 async, await 사용하기

안녕하세요?

오늘은 자바스크립트 비동기 프로그래밍에서 가장 헷갈릴 수 있는 forEach, map 함수에서 async, await 함수 사용에 대해 알아보겠습니다.

결론적으로 말하면

비동기 함수에 있어 Array.map 이나 forEach 함수에서 async, await를 사용하면 안 됩니다.

그럼 어떻게 구현해야 할까요?


예를 들어 볼까요?

제가 개인 프로젝트로 유튜브 API를 사용하고 있는데요.

Prisma를 통해 만들었던 Video 테이블에는 youtube_id가 있습니다.

그럼, Video 테이블에 있는 youtube_id를 배열로 얻었고 다시 유튜브 API를 이용해서 youtube_id를 Array.map 함수를 사용해서 해당 비디오의 정보를 가져오는 함수를 만들고 싶은데요.

단순하게 다음과 같이 하면 되겠다 싶은데요.

let youtubeInfo: YoutubeInfo[]

youtubeInfo = myVideo.map(
  async video => await getYoutubeInfoById(video.youtube_id),
)

// return 결과는?
// [
//   {}, {}, {}, {}, {},
//   {}, {}, {}, {}, {}
// ]

위 코드의 리턴 결과를 잘 보시면 map 함수와 async, await는 절대 같이 실행되지 않습니다.

그럼 어떻게 해야 할까요?


MDN 에서 추천하는 방식은 Promise.all 입니다.

가장 기본이 되는 형식은 아래와 같은데요.

await Promise.all(
    arr.map(async (element) => {
        ....
    })
)

Array.map 함수가 필요하면 그걸 전부 Promise.all로 감싸면 됩니다.

그럼 제 사이드 프로젝트에서는 어떻게 했을까요?

바로 다음 코드를 보십시오.

let youtubeInfos: YoutubeInfo[]
if (!allYoutubeId) {
  return json({})
}
youtubeInfos = await Promise.all(
  allYoutubeId.map(
    async (item): Promise<YoutubeInfo> =>
      await getYoutubeInfoById(item.youtube_id),
  ),
)

return json(youtubeInfos)

제가 원하는 데이터는 youtubeInfos라는 배열 데이터입니다.

이 배열에 원하는 allYoutubeId 배열의 정보를 이용해서 유튜브 정보를 하나씩 추가하면 되는데요.

작동 원리는 다음과 같은데요.

Promise.all에 map 함수를 돌리면 map 함수가 하나씩 실행되는 게 전부 Promise가 된다는 전제에서 map함수와 async, await를 같이 쓸 수 있다는 겁니다.

그래서 위 코드에서도 map 함수 안에 리턴 되는 타입도 Promise<YoutubeInfo>처럼 Promise로 처리했습니다.

이러면 async await를 map 함수에서 돌릴 수 있는데요.

실행 결과는 아주 만족스러웠습니다.

앞으로 배열을 map으로 async, await 하고 싶으면 Promise.all을 이용하면 됩니다.

그럼.