본문 바로가기
FrontEnd Develop/Project : Team Nova MJ Search

비동기 함수와 useEffect에서의 처리 방식 쉽게 이해하기

by Frisbeen 2025. 3. 20.

기본 이해부터  : 비동기 함수 (async/await)를 왜 사용해야 할까?

 

API 요청을 할 때, 서버에서 응답을 받기까지 시간이 걸리기 때문! 
이렇게 말하면 뭐라는지 모를게 뻔하니,

API 호출은 뭡니까 -> 네트워크를 통해 외부 데이터를 가져오는 것
이 떄 네트워크 요청은 응답이 오기까지 시간이 얼마나 걸릴지 예측이 불가능합니다. 

근데 이때 우리가 쓰는 자바스크립트는 동기적으로 작동하는데,
네트워크가 느려서 응답을 기다리는 동안 동기적 작동하는 자바스크립트가 딱 막혀버리면
전체가 막혀버리겠습니다. (blocking) 이라 하죵

흔히 쓰는 크롬 브라우저의 버튼 클릭이랑 화면 조작이 먹통이되는 겁니다.
결론적으로 이를 방지하기 위해서 작업이 완료될때까지 기다리지 않고, 다른 작업을 할 수 있도록 하는 것이 목적입니다.




(async/await) 전에 Promise 전에 CallBack 함수? 발전의 흐름

위에서 우리가 API 요청은 반드시 비동기 처리하라했는데, 만약 프로그램이 API 호출을 기다리지 않고, 다음 코드를 계속 실행하겠습니다.

일차원적으로 생각해봅시다. 결과를 기다리지 않고 슉슉 가는데, 그럼 API 요청의 결과를 어떻게 핸들하지 ? 라는 고민이 시작됩니다.

이때 사용하는 방법이 바로 콜백 함수였던 겁니다.

// API 요청을 보내고, 결과가 준비되면 이 콜백을 호출해줘!
fetchDataFromAPI(function(result) {
    console.log("결과 받음!", result);
});

// 콜백 전달 후 프로그램은 바로 다음 줄로 넘어감.
console.log("요청 보냄!");

 fetchDataFromApi(function(result)) 함수의 역할을 서술하라


이 함수로 Api 요청을 보내고, 준비가 되면 function(result)라는 함수를 실행해줘! 와 같은 역할을 수행하겠습니다.

기본적으로 자바스크립트는 동기적으로 작동한다 했으니까 일단 API 호출 함수가 작동합니다.
그 이후 FetchDataApi 함수 내부에서는 네트워크 요청을 시작하고 즉시 종료됩니다.
그 이후 작업이 끝나면 나중에 전달받은 콜백합니다. (result console logging)

즉시 종료가 되었으니, 그 다음 "요청 보냄!" 이란 그 다음 줄 코드가 실행되겠습니다.

요청 보냄! (즉시 출력됨)
결과 받음! (비동기 작업 완료 후 출력됨)

if -> fetchDataFromAPI가 실행되지 않는다면?

요청 보냄!


그렇다면 프로미스는 무엇인가?
만약 비동기 작업이 무척이나 많아져서, 링크를 잇고 또 이어진다면?
-> 매개변수 안에 매개변수안에... 이러기 때문에 가독성 측면에서 보기 안좋아집니다. <콜백 지옥>

따라서 아래와 같은 프로미스 방식이 나타나게 되었습니다.
그러나 여전히 또 체이닝이 되다보면 오류가 발생합니다.

// 프로미스를 사용한 API 호출
fetchDataFromAPI()
  .then(result => {
    console.log("결과 받음", result);
  })
  .catch(error => {
    console.error("에러 발생", error);
  });

console.log("요청 보냄!");


async/await

최근 트렌드이자, 프로미스를 더 편리하게 작성하는 문법!

"비동기 호출을 동기적으로 보이는 코드"

// async/await 방식으로 작성한 예
async function fetchData() {
  try {
    const result = await fetchDataFromAPI();
    console.log("결과 받음!", result);
  } catch (error) {
    console.error("에러 발생", error);
  }
}

fetchData();
console.log("요청 보냄!");

자 그럼 여길 봅시다.
아래처럼 출력하면 "데이터 도착!" 이란 값이 나와야겠죠? 그러나..

async function fetchData() {
  return "데이터 도착!";
}

console.log(fetchData());

 

Promise { '데이터 도착!' }

이런 결과가 나옵니다. -> 비동기 함수 async function은 return 값이 무조건 promise를 반환한다.

왜요?

왜냐면 비동기함수의 결과는 값이 아니라 이 값을 줄게라는 약속을 주는거라니깐용

 

따라서 실제 그 약속을 이행해서 가져오려면 await을 사용해야한다.

 

안쓰면 이렇게되겠죵

async function fetchData() {
  fetch("https://jsonplaceholder.typicode.com/todos/1")
    .then(response => response.json())
    .then(data => console.log(data));
}

function main() {
  const result = fetchData(); // ❌ 비동기 실행, but 값을 기다리지 않음
  console.log(result); // ❌ Promise가 출력됨
}

main();
Promise { <pending> }
{ id: 1, title: 'delectus aut autem', completed: false } (나중에 도착)

 

 useEffect에서 비동기함수로 API  가져올 때 주의할 점

 

useEffect는 일반적인 함수이고, 비동기(async) 함수를 직접 사용할 수 없음.

 

❌ 잘못된 예시

useEffect(async () => {
  const data = await getWeeklyMenu(); // ❌ useEffect를 async로 만들 수 없음
  setMealData(data);
}, []);

이렇게 하면 useEffect가 Promise를 반환하게 되어 React에서 오류가 발생할 수 있음.

useEffect의 첫번째 규칙 "useEffect의 첫번째 콜백함수는 동기함수여야한다"

하지만, async function 비동기 함수는 항상 Promise를 반환하기도하고 애초에 비동기함수니까 안돼

 

✅ 올바른 예시: async 함수를 따로 선언하고 실행하기

useEffect(() => {
  const fetchMealData = async () => {
    try {
      const data = await getWeeklyMenu();
      setMealData(data || []);
    } catch (error) {
      console.error('식단 데이터를 불러오는 중 오류 발생', error);
    }
  };

  fetchMealData(); // 선언한 함수를 실행!
}, []);

 

 이 방식이 올바른 이유:         

 

useEffect 자체는 async가 아니므로, 비동기 함수를 내부에서 따로 선언

내부에서 만든 async 함수에서 await을 사용해 데이터를 기다린 후, setMealData()를 호출.

마지막으로 fetchMealData();를 실행해서 비동기 요청을 수행.


정리 

비동기 API 함수는 await을 사용해야 응답을 받을 때까지 기다릴 수 있음.

await을 사용하려면 반드시 async 함수여야 함.

 useEffect는 async를 직접 사용할 수 없으므로, 내부에서 async function을 선언하고 실행해야 함.

 

💡 이렇게 하면 useEffect에서도 안전하게 API 데이터를 불러와 사용할 수 있다!