자바스크립트에서 동기(Synchronous)와 비동기(Asynchronous)는 작업을 처리하는 방식을 나타낸다.
이 개념을 이해하면 코드 실행 흐름을 더 효과적으로 관리할 수 있으며, 특히 네트워크 요청, 파일 읽기, UI 업데이트 등의 비동기 작업을 다룰 때 매우 중요하다.
📌 동기 실행 방식은 코드가 순차적으로 실행되며, 하나의 작업이 끝나야 다음 작업이 실행되는 방식이다.
✅ 특징
✔️ 코드가 작성된 순서대로 실행
✔️ 하나의 작업이 끝날 때까지 다음 코드가 실행되지 않음 (Blocking)
✔️ 실행 흐름이 직관적이며 예측하기 쉬움
✅ 예제
console.log("A");
console.log("B");
console.log("C");
💡 실행 결과 → A → B → C
모든 코드가 작성된 순서대로 실행된다.
✅ 문제점
동기 방식은 하나의 작업이 끝날 때까지 다음 작업이 실행되지 않기 때문에 시간이 오래 걸리는 작업이 있을 경우 전체 프로그램이 멈출 수 있다.
console.log("A");
for (let i = 0; i < 1e9; i++) {} // 오래 걸리는 작업
console.log("B");
💡 "A"를 출력한 후, 반복문이 끝날 때까지 "B"는 출력되지 않는다.
즉, CPU가 한 가지 작업에 묶여 다른 작업을 처리하지 못하는 문제(Blocking)가 발생한다.
📌 비동기 실행 방식은 작업을 백그라운드에서 실행하며, 현재 작업이 끝나지 않아도 다음 작업을 계속 실행하는 방식이다.
✅ 특징
✔️ 작업을 동시에 처리할 수 있음 (Non-blocking)
✔️ 실행 순서를 예측하기 어려울 수 있음
✔️ 주로 네트워크 요청, 파일 읽기, 타이머(setTimeout) 같은 작업에서 사용됨
✅ 비동기 예제 (setTimeout)
console.log("A");
setTimeout(() => {
console.log("B");
}, 2000);
console.log("C");
💡 실행 결과 → A → C → (2초 후) B
setTimeout이 실행될 때 바로 멈추지 않고 다음 코드(C)를 실행한 후, 지정된 시간(2초)이 지나면 B가 실행된다.
✅ 비동기 작업의 처리 방식
자바스크립트는 싱글 스레드(Single Thread)이지만, 비동기 작업을 효율적으로 관리하기 위해 이벤트 루프(Event Loop)와 콜백 큐(Callback Queue)를 사용한다.
setTimeout, fetch)은 웹 API가 실행 후 백그라운드에서 처리📌 콜백 함수는 특정 작업이 끝난 후 실행되는 함수이다.
function fetchData(callback) {
setTimeout(() => {
console.log("데이터 받아오기 완료");
callback();
}, 2000);
}
fetchData(() => {
console.log("데이터를 화면에 표시");
});
💡 실행 결과 → (2초 후) 데이터 받아오기 완료 → 데이터를 화면에 표시
콜백을 활용하면 비동기 작업이 끝난 후 원하는 작업을 실행할 수 있다.
✅ 콜백 지옥(Callback Hell) 문제
콜백 함수가 중첩될 경우 코드가 복잡해지고 가독성이 떨어지는 문제가 발생한다.
setTimeout(() => {
console.log("1초 후 실행");
setTimeout(() => {
console.log("2초 후 실행");
setTimeout(() => {
console.log("3초 후 실행");
}, 1000);
}, 1000);
}, 1000);
💡 콜백이 중첩되면서 코드가 읽기 어려워지는 문제 발생
이 문제를 해결하기 위해 Promise와 async/await이 등장했다.
📌 Promise는 비동기 작업의 결과(성공 또는 실패)를 나타내는 객체이다.
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("데이터 받아오기 완료");
}, 2000);
});
};
fetchData().then((result) => {
console.log(result);
}).catch((error) => {
console.log("에러 발생", error);
});
💡 실행 결과 → (2초 후) 데이터 받아오기 완료
resolve()가 호출되면 then() 블록이 실행reject()가 호출되면 catch() 블록이 실행✅ Promise 체이닝 (Chaining)으로 콜백 지옥 해결
fetchData()
.then((data) => {
console.log(data);
return "추가 데이터 처리";
})
.then((message) => {
console.log(message);
})
.catch((error) => {
console.log("에러 발생", error);
});
💡 then()을 체이닝하면 가독성이 좋아지고, 순차적인 비동기 작업을 쉽게 처리 가능
📌 async/await은 Promise 기반의 비동기 코드를 더 직관적으로 작성할 수 있도록 도와준다.
const fetchData = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve("데이터 받아오기 완료");
}, 2000);
});
};
async function getData() {
console.log("데이터 요청");
const data = await fetchData();
console.log(data);
}
getData();
💡 실행 결과 → 데이터 요청 → (2초 후) 데이터 받아오기 완료
await 키워드를 사용하면 비동기 작업이 완료될 때까지 기다린 후 다음 코드 실행try...catch를 사용하면 에러 처리를 쉽게 할 수 있음✅ async/await의 에러 처리
async function getData() {
try {
const data = await fetchData();
console.log(data);
} catch (error) {
console.log("에러 발생", error);
}
}
💡 try...catch를 활용해 에러를 직관적으로 처리 가능
| 동기(Synchronous) | 비동기(Asynchronous) | |
|---|---|---|
| 실행 방식 | 코드가 순차적으로 실행됨 | 작업이 완료되지 않아도 다음 코드 실행 가능 |
| 작업 흐름 | 한 번에 하나의 작업만 수행 (Blocking) | 여러 작업을 동시에 처리 가능 (Non-blocking) |
| 사용 예시 | console.log(), 일반 연산 | setTimeout(), fetch(), Promise |
| 문제점 | 긴 작업이 있으면 전체 실행이 멈춤 | 실행 순서가 예측하기 어려울 수 있음 |
| 해결 방법 | 멀티스레딩 필요 | Promise, async/await |
✔️ 동기 방식은 실행 순서가 직관적이지만 시간이 오래 걸리는 작업에서 블로킹 발생
✔️ 비동기 방식은 작업을 동시에 실행하여 UI 반응성을 높이고 성능을 최적화
✔️ 콜백 함수 → Promise → async/await 순으로 발전하여 더 직관적인 비동기 코드 작성 가능 🚀