지금까지의 프로젝트에서 비동기에 대한 어려움을 많이 겪었기에 비동기에 대한 개념을 확실하게 잡고 넘어가기로 했다.
둘을 구분짓는 가장 쉬운 키워드는 순차라고 할 수 있다.
요청을 보내면 응답이 올 때까지 대기한다. 일의 순서가 중요한 경우 동기 처리하는 것. 예를 들면 외부 데이터를 json 형태로 통신하여 받아온 후, 해당 내용을 브라우저 상에 react 컴포넌트로 변환하여 제공해야 할 때 필요한 처리이다.
대기하는 동안 다른 작엄을 수행할 수 없기 때문에 비효율적임
요청을 보내고, 응답을 기다리지 않은 채 다른 작업을 계속 진행한다. 일의 순서가 중요하지 않은 경우에 비동기 처리를 한다.
아래와 같은 경우는 비동기적 처리일까, 동기적 처리일까?
console.log("시작");
setTimeout(() => {
console.log("타임아웃");
}, 1000);
console.log("끝");
답은 비동기적 처리이다. 만약 동기적 처리라면 타임아웃이 출력되는 1초를 기다렸다가 끝이 출력될 것이다. setTimeout 함수는 지정된 시간(여기서는 1000ms) 후에 콜백 함수를 실행하는 비동기 함수이다.
fetch는 외부 데이터를 가져오기 때문에 비동기적으로 작동할 수 밖에 없다. 따라서 동기적 처리를 위해서는 await 처리나 then 처리 해주어야 한다. 아래 예시에서 fetch가 비동기적으로 동작하여 끝이 먼저 출력되는 걸 확인할 수 있다.
import React, { useState, useEffect } from "react";
function App() {
const [post, setPost] = useState(null);
useEffect(() => {
console.log("시작");
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then((response) => response.json())
.then((json) => {
setPost(json);
console.log("데이터 가져오기 완료");
});
console.log("끝");
}, []);
return <div>{post ? <div>{post.title}</div> : <div>Loading...</div>}</div>;
}
export default App;
왜 사용할까?
자바스크립트는 비동기 작업을 처리할 때 콜백 함수를 많이 사용한다. 콜백 함수를 중첩해서 사용하다 보면 코드가 복잡해지기 때문에 유지보수가 어렵고 가독성이 떨어진다. (이를 콜백 지옥이라고 부른다.)
이행 : resolve로 pending에서 이 상태로 변경한다. resolve 함수가 호출되면 그 값이 then 메서드의 callback 함수로 전달된다.
resolve("작업이 성공적으로 완료되었습니다!");
위 코드에서 "작업이 성공적으로 완료되었습니다!"가 callback 함수로 전달된다.
실패 : reject로 pending에서 이 상태로 변경한다. 마찬가지 원리로, reject 함수가 호출되면 그 값이 catch 메서드의 callback 함수로 전달된다.
대기 : 초기 상태이다.
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve("작업이 성공적으로 완료되었습니다!");
} else {
reject("작업이 실패했습니다.");
}
}, 2000); // 2초 후에 작업이 완료
});
myPromise
.then((result) => {
console.log(result); // "작업이 성공적으로 완료되었습니다!" 출력
})
.catch((error) => {
console.log(error); // "작업이 실패했습니다." 출력
});

기억하기! fetch 함수는 promise 객체를 반환! response Object를 가진 것을 resolve하는 promise를 return.
코드
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => response.json())
.then((json) => console.log(json));
output
{
id: 1,
title: '...',
body: '...',
userId: 1
}
배열 안의 요청을 보내고, 처리 순서와 상관없이 모두가 다 처리되었을 때 결과를 한꺼번에 처리한다.
useEffect(() => {
Promise.all([
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => response.json()),
fetch('https://jsonplaceholder.typicode.com/posts/2')
.then((response) => response.json())
]).then(function ([response1, response2]) { //구조분해할당
console.log('response -> ', response1);
console.log('response -> ', response2);
});
}, []);
then보다 더 많이 쓰이는 것이 바로 이 두 가지 키워드이다. async 함수는 항상 Promise를 반환한다. 함수 내부에서 명시적으로 return 문으로 값을 반환하면 자동으로 Promise.resolve()를 통해 감싸져서 반환된다. await 키워드는 Promise가 이행될 때까지 기다리게 한다. await는 async 함수 내부에서 사용할 수 있다. 비동기 코드를 더 동기 코드처럼 작성할 수 있게 한다. 외부데이터를 가져온 후 데이터 처리를 해야하는 경우가 많기 때문에 매우 중요하다.
const [post, setPost] = useState(null);
useEffect(() => {
const fetchPost = async () => {
const response = await fetch(
"https://jsonplaceholder.typicode.com/posts/1"
);
const data = await response.json();
setPost(data);
};
fetchPost();
}, []);
try-catch문을 통해 rejected 상태까지 처리한다.
import React, { useState, useEffect } from "react";
function App() {
const [post, setPost] = useState(null);
useEffect(() => {
const fetchPost = async () => {
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/posts/1"
);
const data = await response.json();
setPost(data);
} catch (error) {
console.error("Error fetching post:", error);
}
};
fetchPost();
}, []);
return <div>{post ? <div>{post.title}</div> : <div>Loading...</div>}</div>;
}
export default App;