map은 await을 기다리지 않는다.

About JS, JS미세팁
2023/10/20

async / await

async / await은 ES6에서 추가된 문법으로, Promise 기반의 비동기 코드를 읽기 쉽고 직관적으로 만들어줍니다.

async 키워드는 함수를 비동기 함수로 만듭니다. async가 선언된 함수 내에서 await 키워드를 사용하여 비동기 작업이 완료될 때까지 기다릴 수 있습니다. await은 Promise가 완료될 때까지 함수의 실행을 일시 중단합니다. 그리고 Promise가 완료되면 해당 값을 반환합니다.

async function fetchData() {
  const response = await fetch("https://api.example.com/data");
  const data = await response.json();
  return data;
}

왜?

map() 메서드의 콜백 인자로 async 함수를 전달하고 await 처리도 잘 했는데 왜 Promise 객체가 그대로 반환될까요?

해당 콜백함수가 동작하는 환경에 대해 생각해봐야 합니다. map() 메서드는 Array 클래스의 메서드 중 하나로, 구현체는 아래와 비슷할 거에요.

Array.prototype.map = function (callback) {
  const result = [];
  for (let i = 0; i < this.length; i++) {
    const mappedValue = callback(this[i], i, this);
    result.push(mappedValue);
  }
  return result;
};

callback의 인자로 async 함수를 전달한다면 동작할까요? 그렇지 않겠죠.

비동기 처리가 가능하려면 map 메서드 자체가 async 함수가 되어 callback의 반환 값을 기다려야 합니다.

아래와 같이요.

Array.prototype.map = async function (callback) {
  const result = [];
  for (let i = 0; i < this.length; i++) {
    const mappedValue = await callback(this[i], i, this);
    result.push(mappedValue);
  }
  return result;
};

이처럼 map() 메서드 자체가 비동기 처리를 지원하지 않기 때문에 async 함수를 인자로 전달해도 원하는대로 동작하지 않습니다.

콜백함수가 이행되지 않은 Promise 객체를 그대로 반환하며, 따라서 결과값으로 Promise 객체 배열을 얻게 됩니다.

await 키워드는 Promise 객체만 이행할 수 있기 때문에 아래의 await 또한 아무것도 기다리지 않게 되죠.

const arr = await map(someAsyncFunction); // Promise 배열

이는 Array의 순회 메서드 forEach(), reduce(), filter()등이 모두 동일합니다.

어떻게?

그렇다면 어떻게 처리해야 좋을까요

  1. for문 사용 선언형을 포기하고 순차적으로 실행할 수 있습니다.
for (let i = 0; i < arr; i++) {
  const result = await someAsyncFunc(arr[i]);
  resultArr.push(result);
}

// for...of 도 동일

아래 방식보다 간지가 안나고 모든 비동기 요청을 순차적으로(waterfall) 처리해야 한다는 단점이 있습니다.

  1. Promise.all()
function someFunction(array) {
  return array.map(async (item) => await someAsyncFunction(item));
}

위 함수는 Promise 객체 배열을 반환한다고 했었죠. Promise.all() 을 통해 순회 가능한 객체 내부의 Promise의 이행을 기다리는 Promise를 받을 수 있습니다.

[Promise.all() 이란? ->] (https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)

async function someFunction(array) {
	  return await Promise.all(array.map(async (item) => {
		   return await someAsyncFunction(item)
      }));

 ...

 const result = async someFunction(arr); // 정상 동작

다음과 같이 사용할 수 있습니다.

위와 같이 처리했을 때 장점은 map() 메서드가 이행 전의 Promise 객체를 동기적으로 파바박 반환하고, Promise.all() 은 해당 객체들을 병렬적으로 처리할 수 있다는 점이에요.