
동기와 비동기 프로그래밍의 모든 것
웹 개발을 하다 보면 "동기"와 "비동기"라는 용어를 자주 접하게 됩니다. 이 두 개념은 프로그램이 작업을 처리하는 방식의 근본적인 차이를 나타내며, 올바르게 이해하고 사용하는 것이 성능 좋은 애플리케이션을 만드는 핵심입니다.
동기(Synchronous) 프로그래밍이란?
동기 프로그래밍은 작업이 순차적으로 실행되는 방식입니다. 한 작업이 완전히 끝나야 다음 작업이 시작됩니다.
console.log("작업 1 시작");
console.log("작업 2 시작");
console.log("작업 3 시작");
// 출력: 작업 1 시작 → 작업 2 시작 → 작업 3 시작
동기 방식의 특징
- 예측 가능한 실행 순서: 코드가 작성된 순서대로 실행됩니다.
- 간단한 디버깅: 흐름을 추적하기 쉽습니다.
- 블로킹(Blocking): 하나의 작업이 끝날 때까지 다음 작업이 대기합니다.
동기 방식의 문제점
만약 서버에서 데이터를 가져오는 작업이 5초가 걸린다면, 그 5초 동안 프로그램 전체가 멈춰있게 됩니다. 이는 사용자 경험을 크게 해칩니다.
// 나쁜 예: 동기 방식으로 데이터 가져오기
const data = fetchDataSync(); // 5초 대기...
console.log(data); // 5초 후에야 실행됨
console.log("다음 작업"); // 역시 5초 후에야 실행됨
비동기(Asynchronous) 프로그래밍이란?
비동기 프로그래밍은 작업을 동시에 처리할 수 있는 방식입니다. 시간이 오래 걸리는 작업을 백그라운드에서 처리하면서, 다른 작업을 계속 진행할 수 있습니다.
console.log("작업 1 시작");
setTimeout(() => {
console.log("작업 2 완료 (2초 후)");
}, 2000);
console.log("작업 3 시작");
// 출력: 작업 1 시작 → 작업 3 시작 → (2초 후) 작업 2 완료
비동기 방식의 특징
- 논블로킹(Non-Blocking): 다른 작업을 기다리지 않고 계속 실행됩니다.
- 효율적인 리소스 사용: CPU가 대기 시간 없이 다른 작업을 처리할 수 있습니다.
- 복잡한 흐름 제어: 콜백, Promise, async/await 등의 패턴이 필요합니다.
비동기 처리 방법
1. 콜백(Callback)
fetchData((error, data) => {
if (error) {
console.error("에러 발생:", error);
} else {
console.log("데이터:", data);
}
});
2. Promise
fetchData()
.then(data => console.log("데이터:", data))
.catch(error => console.error("에러:", error));
3. Async/Await (가장 권장)
async function getData() {
try {
const data = await fetchData();
console.log("데이터:", data);
} catch (error) {
console.error("에러:", error);
}
}
alert()를 남용하면 안 되는 이유
alert()는 동기적으로 작동하는 블로킹 함수입니다. alert()가 실행되면:
- 전체 JavaScript 실행이 중단됩니다.
- 사용자가 확인 버튼을 누를 때까지 모든 작업이 멈춥니다.
- 비동기 작업도 일시 중지됩니다.
// 나쁜 예
console.log("시작");
alert("이 창을 닫을 때까지 모든 것이 멈춥니다!");
console.log("종료"); // alert 창을 닫기 전까지 실행 안 됨
alert() 대신 사용할 것
Toast 알림 (비동기, 논블로킹):
import { toast } from 'sonner';
toast.success("작업이 완료되었습니다!");
모달 다이얼로그 (비동기 제어 가능):
const confirmed = await showConfirmDialog("정말 삭제하시겠습니까?");
if (confirmed) {
deleteItem();
}
상황별 선택 가이드
동기 방식을 사용해야 할 때
- 간단한 계산 작업: 수학 연산, 문자열 처리 등
- 순서가 중요한 작업: 파일 읽기 → 처리 → 저장
- 초기화 작업: 설정 파일 로드 등
비동기 방식을 사용해야 할 때
- 네트워크 요청: API 호출, 파일 다운로드
- 파일 I/O: 대용량 파일 읽기/쓰기
- 타이머: setTimeout, setInterval
- 사용자 이벤트: 클릭, 스크롤 등
- 데이터베이스 쿼리: 조회, 삽입, 업데이트
실전 예제: 사용자 데이터 가져오기
// 나쁜 예: 동기 방식
function getUserDataBad(userId) {
const user = fetchUserSync(userId); // 블로킹!
const posts = fetchPostsSync(userId); // 블로킹!
return { user, posts };
}
// 좋은 예: 비동기 방식
async function getUserDataGood(userId) {
// 병렬로 실행하여 시간 절약
const [user, posts] = await Promise.all([
fetchUser(userId),
fetchPosts(userId)
]);
return { user, posts };
}
- 동기: 순차적, 예측 가능, 간단하지만 느림
- 비동기: 동시 처리, 빠르지만 복잡함
- alert() 남용 금지: 전체 실행을 멈추는 블로킹 함수
- 상황에 맞는 선택: 작업의 특성에 따라 적절한 방식 사용
현대 웹 개발에서는 대부분의 I/O 작업에 비동기 방식을 사용하는 것이 표준입니다. async/await 문법을 마스터하면 비동기 코드를 동기 코드처럼 읽기 쉽게 작성할 수 있습니다.
