자바스크립트에서 동기(synchronous)와 비동기(asynchronous)는 코드 실행 방식에 대한 개념으로, 프로그램의 흐름과 처리 방식에 큰 영향을 미친다.
1. 동기(Synchronous)
순차적으로 실행되는 방식.
앞의 작업이 끝나야 다음 작업이 실행된다.
funciton print() {
console.log("2");
}
console.log("1");
print();
console.log("3");
출력순서 : 1 -> 2 -> 3
- 위 코드는 순서대로 실행되며, 각각이 끝난 뒤에 다음 줄로 넘어간다.
- 블로킹(blocking): 앞 작업이 오래 걸리면 뒤 작업이 지연됨.
- CPU와 메모리를 기다리는 동안 낭비할 수 있음.
2. 비동기(Asynchronous)
작업을 요청한 후 바로 다음 작업을 수행하며, 완료되면 알림(콜백, Promise, async/await 등)으로 처리하는 방식.
console.log("1");
setTimeout(() => {
console.log("2");
}, 1000);
console.log("3");
출력 순서: 1 -> 3 -> 2
- setTimeout은 1초 뒤 실행되지만, 그 기다리는 동안 console.log("3")가 먼저 실행됨.
- 논블로킹(non-blocking): 시간이 걸리는 작업(예: 서버 통신, 파일 읽기 등)을 기다리지 않고 다음 작업 실행.
- 콜백(callback), Promise, async/await 등을 사용해서 완료된 시점을 처리함.
3. 비교
| 항목 | 동기(Synchronous) | 비동기(Asynchronous) |
| 실행 방식 | 순차적 | 병렬적으로 요청 후 알림 |
| 처리 흐름 | 앞 작업이 끝나야 다음 실행 | 요청하고 기다리지 않음 |
| 예시 함수 | alert, prompt | setTimeout, fetch, addEventListener |
| 단점 | 오래 걸리면 전체 멈춤 | 흐름 추적이 어려울 수 있음 |
| 해결 방법 | - | 콜백, Promise, async/await |
4. 비동기 처리 방식 예시
콜백 지옥
function orderFood(callback) {
setTimeout(() => {
const food = "떢볶이";
callback(food);
}, 3000);
}
function cooldownFood(food, callback) {
setTimeout(() => {
const cooldownedFood = `식은 ${food}`;
callback(cooldownedFood);
}, 2000);
}
function freezdFood(food, callback) {
setTimeout(() => {
const freezedFood = `냉동된 ${food}`;
callback(freezedFood);
});
}
orderFood((food) => {
console.log(food);
cooldownFood(food, (cooldownedFood) => {
console.log(cooldownedFood);
freezdFood(cooldownedFood, (freezedFood) => {
console.log(freezedFood);
});
});
});
Promise
비동기의 흐름을 제어할수 있고 콜백지옥을 간결하게 처리할수 있다.
const p = new Promise((resolve, reject) => {
// 비동기 작업 실행하는 함수
setTimeout(() => {
const num = null;
if (typeof num === "number") {
// 성공처리
resolve(num + 10);
} else {
// 실패처리
reject("num이 숫자가 아닙니다.");
}
}, 2000);
});
// then 메서드
p.then((value) => {
console.log(value); // num + 10
});
p.catch((error) => {
console.log(error); // num이 숫자가 아닙니다.
});
아래와 같이 변경 가능
// then 메서드
p
.then((value) => {
console.log(value); // num + 10
}).catch((error) => {
console.log(error); // num이 숫자가 아닙니다.
});
메서드 적용
function add(num) {
const p = new Promise((resolve, reject) => {
// 비동기 작업 실행하는 함수
setTimeout(() => {
const num = null;
if (typeof num === "number") {
// 성공처리
resolve(num + 10);
} else {
// 실패처리
reject("num이 숫자가 아닙니다.");
}
}, 2000);
});
return p;
}
/*
const p = add(0);
p.then((value) => {
console.log(value);
const p2 = add(value);
p2.then((result) => {
console.log(result);
});
});
*/
/*
const p = add(0);
p.then((value) => {
console.log(value);
const p2 = add(value);
return p2;
}).then((result) => {
console.log(result);
});
*/
add(0)
.then((value) => {
console.log(value);
return add(value);
})
.then((result) => {
console.log(result);
return add(result);
})
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});
;
async/await
async
어떤 함수를 비동기 함수로 만들어주는 키워드
함수가 프로미스를 반환하도록 변환해준다.
async function getDate() {
return {
name: "홍길동",
id: "hongdong",
};
}
console.log(getDate());
함수를 리턴해보면 Promise 객체임을 알수 있다.
async function getDate() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: "홍길동",
id: "hongdong",
});
}, 1500);
});
}
console.log(getDate());
위로직은 어차피 async는 Promise를 리턴해서 async 키워드를 빼도 된다.
function getDate() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: "홍길동",
id: "hongdong",
});
}, 1500);
});
}
console.log(getDate());
await
async 함수 내부에서만 사용이 가능 한 키워드 비동기 함수가 다 처리되기를 기다리는 역할
function getDate() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
name: "홍길동",
id: "hongdong",
});
}, 1500);
});
}
function printDate() {
console.log("print start..");
getDate().then((data) => {
console.log("print: "+data);
});
}
async function printDate2() {
console.log("print2 start..");
const data = await getDate();
console.log("print2: "+data);
}
function printDate3() {
console.log("print3 start..");
const data = getDate();
console.log("print3:" + data);
}
console.log(getDate()); // -> pending 상태로 출력
printDate();
printDate2();
//printData3(); // -> 에러
5. 이벤트 루프
이벤트 루프(Event Loop)는 자바스크립트에서 비동기 처리를 가능하게 해주는 핵심 메커니즘이다.
자바스크립트가 싱글 스레드임에도 UI가 멈추지 않고, 비동기 작업을 처리할 수 있도록 돕는 구조이기도 하다.
이벤트 루프 작동 방식이란?
이벤트 루프는 아래의 핵심 컴포넌트 4가지를 계속 감시하며 동작한다.
1. Call Stack (콜 스택)
현재 실행 중인 함수들이 쌓이는 공간
2. Web APIs (브라우저 기능)
비동기 작업(ex. setTimeout, fetch, addEventListener)을 처리하는 외부 영역
3. Callback Queue (태스크 큐)
비동기 작업이 완료되어 콜백 실행을 기다리는 함수들이 줄 서 있는 공간
4. Event Loop (이벤트 루프)
콜 스택이 비면, 콜백 큐에서 함수를 가져와 실행하는 중재자
작동 순서
console.log("A");
setTimeout(() => {
console.log("B");
}, 0);
console.log("C");
실행 흐름
1. console.log("A") → 콜 스택에 쌓이고 실행 → 출력: A
2. setTimeout(...) → Web API로 전달 → 0ms 타이머 시작
3. console.log("C") → 콜 스택에 쌓이고 실행 → 출력: C
4. 타이머 0ms 후 콜백 함수 () => console.log("B")가 Callback Queue에 들어감
5. Event Loop가 콜 스택이 비었는지 확인 → 비었으므로 Callback Queue에서 콜백을 가져와 실행
6. console.log("B") → 출력: B
출력
A
C
B
그림으로 표현한 개념 요약
┌───────────────┐
│ Call Stack │ ← 현재 실행 중인 코드
└────┬──────────┘
│
▼
┌───────────────┐
│ Web APIs │ ← setTimeout, fetch, DOM event 등
└────┬──────────┘
│
▼
┌───────────────┐
│ Callback Queue│ ← 완료된 비동기 작업의 콜백이 줄 서는 곳
└────┬──────────┘
│
▼
┌───────────────┐
│ Event Loop │ ← 콜 스택이 비면 큐에서 하나씩 꺼내 실행
└───────────────┘
6. Macrotask vs Microtask
자바스크립트는 콜백 큐가 2종류로 구분
| 구분 | 종류 | 예시 |
| Macrotask Queue | setTimeout, setInterval, setImmediate | 큰 작업 단위 |
| Microtask Queue | Promise.then, async/await, queueMicrotask | 더 빠르게 실행됨 |
이벤트 루프는 항상
1. 콜 스택이 비면 → Microtask Queue 먼저 모두 처리
2. 그 다음에 Macrotask Queue에서 1개만 처리
결론
| 구성요소 | 역할 |
| Call Stack | 함수 실행 순서 관리 |
| Web API | 비동기 작업 위임 및 완료 처리 |
| Callback Queue | 완료된 작업의 콜백 보관 |
| Event Loop | 콜 스택이 비면 큐에서 콜백을 가져와 실행 |
| Microtask Queue | 더 빠르게 실행되는 Promise 등 처리 |
7. 자바스크립트에서 왜 비동기가 중요한가?
자바스크립트는 단일 스레드(single-thread) 기반 언어.
즉, 한 번에 하나의 작업만 수행할 수 있으므로, 시간이 오래 걸리는 작업이 있을 경우, 전체 UI가 멈출 수 있다.
이 문제를 해결하기 위해 비동기 방식을 사용하여 다음과 같이 동작한다.
- 네트워크 요청
- 타이머 (setTimeout, setInterval)
- 파일 I/O
- 이벤트 리스너
이런 작업들은 Web APIs, 이벤트 루프(Event Loop), 콜백 큐(Callback Queue)를 통해 처리된다.
자바스크립트는 싱글 스레드(Single Thread) 기반 언어이다. 즉, 한 번에 하나의 작업만 처리할 수 있다.
그럼에도 불구하고 비동기 처리가 가능한 이유는 브라우저나 Node.js가 제공하는 런타임 환경 덕분이다.
8. 싱글 스레드란?
- 자바스크립트는 단일 실행 컨텍스트, 즉 하나의 콜 스택(Call Stack)을 이용해 코드를 처리한다.
- 동시에 여러 작업을 수행하지 않고, 작업을 하나씩 순차적으로 처리한다.
그럼에도 불구하고, 다음과 같은 비동기 작업을 자주 사용한다.
- setTimeout, setInterval
- fetch, XMLHttpRequest
- 이벤트 처리 (addEventListener)
- 파일 읽기 (Node.js 등)
9. 비동기 처리 구조 – 자바스크립트 런타임 아키텍처
비동기 처리는 아래 4가지 컴포넌트가 핵심이다
1. Call Stack (콜 스택)
실행할 함수들이 쌓이는 공간입니다. 싱글 스레드이므로 한 번에 하나씩만 실행한다.
2. Web APIs (브라우저 환경 제공 기능)
비동기 API를 실행할 수 있도록 브라우저가 제공하는 영역이다.
예: setTimeout, fetch, DOM 이벤트, 타이머, Ajax 등
3. Callback Queue (태스크 큐)
Web API에서 완료된 작업의 콜백 함수들이 대기하는 큐이다.
4. Event Loop (이벤트 루프)
콜 스택이 비어 있는지 확인하여, 콜백 큐에 있는 작업을 콜 스택으로 올려주는 역할을 한다.
10. 전체 흐름 설명
console.log("1");
setTimeout(() => {
console.log("2");
}, 1000);
console.log("3");
동작 과정
1. console.log("1") → 콜 스택에서 실행 → 바로 출력됨
2. setTimeout() → Web API에 전달됨 → 1초 대기
3. console.log("3") → 콜 스택에서 실행 → 바로 출력됨
4. 1초 후 Web API가 콜백(() => console.log("2"))을 Callback Queue에 추가
5. Event Loop가 콜 스택이 비었음을 확인 후 → Callback Queue의 console.log("2")를 콜 스택에 전달
6. 콜 스택에서 console.log("2") 실행
출력
1
3
2
11. 자바스크립트 비동기의 핵심: Event Loop
Event Loop는 아래와 같은 루프를 무한 반복한다.
1. 콜 스택이 비어있는가?
- 아니면 → 기다림
- 맞으면 → Callback Queue에서 작업 꺼냄
2. 콜백을 콜 스택에 올려 실행
3. 다시 1번으로 돌아감
이 구조 덕분에, 자바스크립트는 싱글 스레드임에도 비동기 처리가 가능한 것이다.
12. 비동기 방식 구현 방법 요약
구현 방식 설명 코드 예시
| 콜백 함수 | 함수 호출 시 완료 후 실행될 함수 전달 | setTimeout(() => {...}, 1000) |
| Promise | 비동기 작업의 성공/실패를 나중에 처리 | fetch(...).then(...) |
| async/await | 비동기 코드를 동기식처럼 표현 | await fetch(...) |