| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- Docker Compose
- podman
- glm-ocr
- docker
- PowerShell
- Thinking Mode
- qwen3-coder-next
- cli
- SharedArrayBuffer
- vscode
- io
- Vite
- Ollama
- podman compose
- yaml
- cross-origin isolated
- sys.path
- curl
- Typescript
- vim
- 이미지
- Webpack
- Java
- RandomAccessFile
- ndjson
- json
- tauri
- secure context
- json schema
- .dockerignore
- Today
- Total
워로디스
JavaScript. setTimeout(fn, 0) 과 async IIFE 의 실행 타이밍 정리 본문
요약
window.setTimeout(fn, 0)과 (async () => {})()는 모두 비동기 문맥에서 자주 보이지만, 실행 타이밍과 의도는 다릅니다.
가장 중요한 차이는 다음과 같습니다.
setTimeout(fn, 0)은 콜백 실행을 다음 macrotask로 미룹니다.(async () => {})()는async함수이지만, 첫 번째await를 만나기 전까지는 즉시 동기적으로 실행됩니다.await이후의 코드는 microtask로 이어집니다.- 일반적으로 microtask는 macrotask보다 먼저 실행됩니다.
1. setTimeout(fn, 0)의 의미
console.log("A");
setTimeout(() => {
console.log("B");
}, 0);
console.log("C");
출력 결과는 다음과 같습니다.
A
C
B
0ms라고 해서 즉시 실행된다는 뜻은 아닙니다. 현재 실행 중인 동기 코드가 모두 끝나고 call stack이 비워진 뒤, 이벤트 루프의 다음 macrotask에서 실행됩니다.
즉, setTimeout(fn, 0)의 의도는 다음과 같습니다.
지금 당장 실행하지 말고, 현재 동기 실행이 끝난 뒤 나중에 실행하라.
2. setTimeout(fn, 0)을 사용하는 대표적인 이유
현재 동기 흐름 이후에 실행하기
현재 함수나 이벤트 핸들러의 실행이 완전히 끝난 뒤 후속 작업을 실행하고 싶을 때 사용합니다.
function updateState() {
state.value = 1;
setTimeout(() => {
console.log("state update 이후 실행");
}, 0);
}
브라우저 렌더링이나 사용자 입력 처리를 끼워 넣기
무거운 작업을 바로 이어서 실행하면 브라우저가 화면을 갱신할 틈이 없을 수 있습니다. 작업을 다음 macrotask로 미루면 브라우저가 중간에 렌더링이나 입력 처리를 수행할 기회를 얻을 수 있습니다.
button.textContent = "처리 중...";
setTimeout(() => {
heavyWork();
button.textContent = "완료";
}, 0);
재진입 문제 피하기
이벤트 핸들러나 콜백 내부에서 바로 다른 로직을 실행하면, 아직 이전 처리가 끝나지 않은 상태에서 코드가 재진입할 수 있습니다. 이런 경우 실행을 다음 이벤트 루프 턴으로 미루면 더 안전한 흐름을 만들 수 있습니다.
실행 순서를 의도적으로 뒤로 보내기
doSomething();
setTimeout(() => {
doAfterEverythingElse();
}, 0);
이런 형태는 현재 동기 실행 이후로 특정 작업을 미루고 싶을 때 사용합니다.
3. (async () => {})()는 같은 효과일까?
겉으로는 비슷해 보일 수 있지만, setTimeout(fn, 0)과는 다릅니다.
console.log("A");
(async () => {
console.log("B");
})();
console.log("C");
출력 결과는 다음과 같습니다.
A
B
C
async 함수라고 해서 함수 본문 전체가 자동으로 나중에 실행되는 것은 아닙니다.
async 함수의 본문은 첫 번째 await를 만나기 전까지는 일반 함수처럼 즉시 실행됩니다.
따라서 다음 코드는 사실상 즉시 실행 함수와 비슷하게 동작합니다.
(async () => {
console.log("즉시 실행됨");
})();
4. await를 사용하면 실행이 뒤로 밀린다
async IIFE 안에서 실제로 이후 코드를 뒤로 미루려면 await가 있어야 합니다.
console.log("A");
(async () => {
await null;
console.log("B");
})();
console.log("C");
출력 결과는 다음과 같습니다.
A
C
B
이 경우 await null 이후의 코드는 현재 동기 코드가 끝난 뒤 실행됩니다.
하지만 이것도 setTimeout(fn, 0)과 완전히 같지는 않습니다. await 이후의 코드는 microtask로 실행되기 때문입니다.
5. microtask와 macrotask의 차이
자바스크립트 이벤트 루프에서는 작업 큐가 여러 종류로 나뉩니다. 여기서 중요한 것은 microtask와 macrotask입니다.
대표적인 예시는 다음과 같습니다.
| 구분 | 예시 |
|---|---|
| microtask | Promise.then(...), queueMicrotask(...), await 이후 코드 |
| macrotask | setTimeout(...), setInterval(...), DOM 이벤트 콜백 등 |
일반적으로 현재 동기 코드가 끝난 뒤에는 microtask가 먼저 처리되고, 그 다음 macrotask가 처리됩니다.
setTimeout(() => console.log("timeout"), 0);
Promise.resolve().then(() => console.log("promise"));
console.log("sync");
출력 결과는 다음과 같습니다.
sync
promise
timeout
Promise.then(...)은 microtask이고, setTimeout(..., 0)은 macrotask이기 때문에 promise가 timeout보다 먼저 출력됩니다.
6. async IIFE와 setTimeout(fn, 0) 비교
setTimeout(() => console.log("timeout"), 0);
(async () => {
await null;
console.log("async after await");
})();
console.log("sync");
출력 결과는 다음과 같습니다.
sync
async after await
timeout
이 코드는 다음 순서로 실행됩니다.
- 동기 코드인
console.log("sync")가 실행됩니다. await null이후 코드가 microtask로 실행됩니다.setTimeout(..., 0)콜백이 macrotask로 실행됩니다.
따라서 async IIFE에 await를 넣으면 “뒤로 미루는 효과”는 있지만, 그 지연 시점은 setTimeout(fn, 0)보다 빠릅니다.
7. 정리
세 가지 코드는 각각 의미가 다릅니다.
즉시 실행되는 async 함수
(async () => {
// 첫 await 전까지는 즉시 실행됨
})();
이 코드는 async 함수이지만, 첫 번째 await를 만나기 전까지는 동기적으로 실행됩니다.
microtask로 미루기
(async () => {
await null;
// 이 이후는 microtask로 실행됨
})();
이 코드는 현재 동기 코드가 끝난 뒤, microtask 단계에서 이어집니다.
비슷한 형태로 다음 코드도 사용할 수 있습니다.
Promise.resolve().then(() => {
// microtask로 실행됨
});
또는 더 명시적으로 다음과 같이 쓸 수 있습니다.
queueMicrotask(() => {
// microtask로 실행됨
});
macrotask로 미루기
setTimeout(() => {
// 다음 macrotask에서 실행됨
}, 0);
이 코드는 현재 동기 코드와 microtask들이 처리된 뒤, 다음 macrotask에서 실행됩니다.
결론
setTimeout(fn, 0)은 “0초 뒤 즉시 실행”이 아니라, 현재 실행 컨텍스트가 끝난 뒤 다음 macrotask로 실행을 미루는 코드입니다.
반면 (async () => {})()는 그 자체로는 코드를 뒤로 미루지 않습니다. 첫 번째 await 전까지는 즉시 실행됩니다.
실행을 뒤로 미루고 싶다면 다음처럼 구분해서 사용하면 됩니다.
| 목적 | 적합한 방식 |
|---|---|
| 현재 동기 코드 이후, 가능한 한 빨리 실행 | queueMicrotask(...), Promise.resolve().then(...), await null |
| 현재 동기 코드와 microtask 이후, 다음 이벤트 루프 턴에서 실행 | setTimeout(fn, 0) |
| 브라우저 렌더링 기회를 주거나 작업을 한 턴 뒤로 넘기기 | setTimeout(fn, 0) |
따라서 setTimeout(fn, 0)과 (async () => {})()는 같은 효과가 아니며, await가 있는 async IIFE도 microtask 기반이므로 setTimeout(fn, 0)과 실행 순서가 다릅니다.
'정리 > JavaScript' 카테고리의 다른 글
| JavaScript. 함수 호출 앞의 void 정리 (0) | 2026.05.15 |
|---|
