Async/Await Inside forEach가 나쁜 생각인 이유

이 문서는 원래 https://maximorlov.com/async-await-inside-foreach/에 게시되었습니다.

여러 비동기 함수를 실행하고 결과를 배열로 푸시했는데 비어 있음을 알게 된 적이 있습니까?



값이 있고(console.log'ed) 값이 푸시되고 있습니다. 어떻게 배열이 비어 있을 수 있습니까?!

너무 혼란 스럽습니다.. 😕

다른 코드를 실행하기 전에 비동기 함수가 완료되기를 기다리는 것은 불가능한 작업처럼 보입니다.

그러나 그것은 어떤 도구를 사용해야 하는지 아직 배우지 않았기 때문입니다.

Promise.all과 .map을 강력한 콤보로 결합하는 방법을 배우면 생각보다 쉽다는 것을 알게 될 것입니다. 😎

forEach 내부의 async/await가 생각한 대로 작동하지 않는 이유



배열의 각 요소에 대해 비동기 작업을 실행해야 하는 경우 자연스럽게 .forEach() 메서드를 사용할 수 있습니다.

예를 들어 사용자 ID 배열이 있고 각 사용자 ID에 대해 사용자를 가져오고 사용자 이름을 배열에 푸시하려고 할 수 있습니다. 마지막에 사용자 이름을 기록하지만 아아, 배열이 비어 있습니다.

const userIds = [1, 2, 3];
const usernames = [];

userIds.forEach(async (userId) => {
  const user = await fetchUserById(userId);
  usernames.push(user.username);
});

// this prints an empty array, no usernames here 🙁
console.log(usernames);


그래서 여기서 무슨 일이 일어나고 있습니까? 이 코드는 실제로 어떻게 실행됩니까? 그리고 그것이 당신이 기대하는 것과 어떻게 다릅니까?

프로그램을 한 줄씩 실행하는 JavaScript 런타임의 애니메이션보다 이를 설명하는 더 좋은 방법은 없습니다.

가장 먼저 주목해야 할 것은 fetchUserById 요청이 forEach 메서드 내에서 병렬로 시작된다는 것입니다. 완료될 때까지 백그라운드 스레드로 전송됩니다(브라우저의 WebAPI, Node.js의 C++ 스레드 풀).

모든 요청이 시작된 후 프로그램은 계속해서 usernames 배열을 기록합니다. 물론 아직 푸시된 것이 없기 때문에 비어 있습니다.

아하!

forEach와 같은 기능적 JavaScript 메서드는 약속을 인식하지 못합니다. 각 반복 사이에 약속을 기다리지 않고 그들의 callback functions are fired off synchronously.

그런 다음 얼마 후 요청이 완료되고 배열이 사용자 이름으로 채워집니다. 그런 다음 프로그램이 종료되고 사용자 이름은 어둠 속에 남습니다. 👀

Note: The order in which the requests finish is random and will rarely be the same order in which they were sent out. This is another reason not to use async/await inside forEach to populate an array — the arrangement of the results will differ from the original array.



프로그래밍에서 가장 먼저 배우는 것 중 하나는 코드가 위에서 아래로 실행된다는 것이기 때문에 런타임이 끝에서 중간으로 "점프 업"하는 것을 보는 것은 혼란스럽습니다.

그렇다면 모든 비동기 작업이 완료된 후에만 일부 코드를 실행하려면 어떻게 해야 할까요?

Promise.all 및 .map , 천상의 일치



해결책은 배열의 각 요소를 약속에 매핑하고 결과 약속 배열을 Promise.all 에 전달하는 것입니다. await 키워드는 단일 약속에서만 작동하기 때문에 Promise.all을 사용해야 합니다.

Promise.all은 애그리게이터와 같습니다. Promise 배열이 주어지면 모든 Promise가 이행된 후에 이행되는 단일 Promise를 반환합니다. 반환된 약속은 입력 약속의 결과가 포함된 배열로 확인됩니다.



예제를 수정하기 위해 각 약속이 사용자 이름으로 확인되는 약속 배열에 사용자 ID를 매핑합니다. 그런 다음 약속을 Promise.all에 전달하고 기다렸다가 결과를 기록합니다.

const userIds = [1, 2, 3];

// Map each userId to a promise that eventually fulfills
// with the username
const promises = userIds.map(async (userId) => {
  const user = await fetchUserById(userId);
  return user.username;
});

// Wait for all promises to fulfill first, then log
// the usernames
const usernames = await Promise.all(promises);
console.log(usernames);


무슨 일이 일어나고 있는지 더 쉽게 이해할 수 있도록 Promise.all에 전달하기 전에 약속을 별도의 변수에 할당했습니다. 실제로는 Promise.all 내부에 직접 작성된 map 함수를 자주 볼 수 있습니다.

그게 다야! 여러 비동기 작업을 실행하고 작업이 완료될 때까지 기다린 후 나머지 코드를 실행하는 방법을 배웠습니다. 👏

다음에 forEach 내에서 async/await가 표시되면 코드가 예상대로 작동하지 않을 수 있으므로 경고 벨을 울려야 합니다. ⚠️

마스터 비동기 자바스크립트 🚀



무료 5일 이메일 과정을 통해 현대적이고 읽기 쉬운 비동기 코드를 작성하는 방법을 배우십시오.

시각적 그래픽을 통해 비동기 코드를 개별 부분으로 분해하고 최신 async/await 접근 방식을 사용하여 다시 결합하는 방법을 배웁니다. 또한 30개 이상의 실제 연습을 통해 지식을 더 나은 개발자가 될 실용적인 기술로 전환할 수 있습니다.



👉 Get Lesson 1 now

좋은 웹페이지 즐겨찾기