0. 들어가며
- PrimeReact 라이브러리를 살펴보다가,
<DataTable />
에 캘린더를 배치하면 캘린더에서 날짜가 변경되지 않는 문제를 발견했다. - 그래서 해결을 위해 발생 지점을 찾아보니,
<DataTable />
을 구성하는<BodyCell />
컴포넌트의 코드가 원인이었다. - 발생 지점도 찾았겠다, 이제 디버깅만 하면 문제는 거의 다 해결된 것과 다름 없었다.
- 그런데 디버깅을 해보니 문제를 일으키는 코드 근처에 눈에 띄는 코드를 발견했다.
- 바로
setTimeout
의delay
를 0으로 설정한 코드였다.
- 처음에는 코드를 잘 못 작성한 건가라는 생각이 들어, 버그의 원인이
setTimeout
부분인 줄 알았다. - 하지만 버그의 원인은 다른 코드에 있었고, “0ms 동안 지연 후에 로직을 실행한다고? 도대체 이 코드의 의도는 뭐지?” 라는 의문이 들었다.
- 그래서 정보를 찾아보니, 0ms가 사실 0ms가 아니라는 걸 알게 되었는데…
- 이번 시간에는
setTimeout
의delay
의 특징을 MDN 문서와 HTML 표준 문서를 기반으로 살펴보았다!
목차
1. setTimeout에는 즉시 실행이란 개념은 없다.
1) setTimeout의 delay 0ms 테스트 해보기
2) delay값이 0ms이거나 생략해도, 지연이 발생하는 이유
(1) 이벤트 루프? 태스크 큐?
(2) 이벤트 루프, 태스크 큐 관점에서 본 setTimeout의 동작
(3) 결국, 즉시 실행은 없다!
2. 마치며
1. setTimeout에는 즉시 실행이란 개념은 없다.
1) setTimeout의 delay 테스트 해보기
setTimeout()
은 주어진delay(ms)
만큼 지연한 뒤, 로직(code
)을 실행하는 타이머 함수이다.- 이때,
delay
는 옵션(optional)이라, 생략이 가능하다.
setTimeout(code, delay)
setTimeout(code)
그럼
delay
를 생략하면, 로직이 즉시 실행되는 걸까?
performance.now()
를 사용해 실행 시간을 측정해 보았다.
See the Pen setTimeout missing delay test by KumJungMin (@kumjungmin) on CodePen.
- 실제로 측정해본 결과,
setTimeout
에서delay
를 생략했음에도 실제로는 0.3~0.6ms 정도 지연 후 코드가 실행되는 것을 확인했다!
그렇다면! primereact의
<BodyCell />
처럼delay
값을 0ms으로 주면 진짜 지연 없이 실행될까?
delay
를 0ms으로 지정해도, 앞선 결과와 유사하게 지연이 발생했다.
See the Pen setTimeout 0ms delay test by KumJungMin (@kumjungmin) on CodePen.
- 게다가
delay
가 0ms인setTimeout
를 5번 이상 중첩 호출하면, 지연 시간이 4ms 이상이 되는 현상도 있었다.
See the Pen nested setTimeout missing delay test by KumJungMin (@kumjungmin) on CodePen.
- 왜
delay
값을 0ms이거나 생략해도 지연이 발생할까? 그리고delay
가 0ms인setTimeout
를 중첩 호출하면 왜 지연 시간이 증가할까?
2) delay값이 0ms이거나 생략해도, 지연이 발생하는 이유
setTimeout
동작을 살펴보기 위해 이벤트 루프와 태스크 큐에 대해 알아야한다.
우선, 이벤트 루프와 태스크 큐의 개념을 간단히 살펴보자!
(1) 이벤트 루프? 태스크 큐?
- 자바스크립트는 싱글 스레드에서 동작하기에 한 번에 하나의 작업만 처리할 수 있다.
- 그래서 여러 작업을 효율적으로 처리하려면 특정한 메커니즘이 필요하다.
- 이때 핵심적인 역할을 하는 게 바로 이벤트 루프(Event Loop)와 태스크 큐(Task Queue)이다.
- 아래 그림은 자바스크립트 런타임 모델의 모습이다.
- 콜 스택(Call Stack)
- 자바스크립트 엔진은 함수를 호출하면, 해당 함수를 콜 스택에 쌓는다.
- 그리고 함수 호출이 종료되면 스택에서 제거한다.
- 콜 스택의 코드는 순차 실행되며, 스택이 비어 있어야 다음 작업을 실행할 수 있다.
- 태스크 큐(Task Queue)
- 브라우저 API(예:
setTimeout
, DOM 이벤트 등)의 콜백 함수들은 태스크 큐에 저장된다. - 이 큐에 있는 콜백 함수들은 콜 스택이 완전히 비었을 때, 이벤트 루프가 콜 스택으로 옮긴 후 실행한다.
- 브라우저 API(예:
- 이벤트 루프(Event Loop)
- 이벤트 루프는 콜 스택을 모니터링하다가, 태스크 큐에 가장 오래 대기 중인 메시지를 꺼내 실행한다.
- 필요한 경우 약간의 지연을 두고 처리하기도 한다.
실제 코드 예시를 보면서, 실행 동작을 살펴보자!
- 아래와 같이
func()
와setTimeout()
이 있다고 가정해보자.
- 그림을 살펴보면, 먼저 콜 스택에
func
함수가 추가된다. func
함수 호출이 완료되면, 콜 스택에서 해당 함수는 제거된다.- 그 다음으로,
setTimeout
의 콜백 함수를 태스크 큐에 등록된다. - 이벤트 루프가 콜 스택이 비어있는지 확인한 후, 만약 비어있다면 태스크 큐의 작업을 콜 스택으로 이동시킨다.
- 이처럼,
setTimeout
은 앞선 콜 스택이 비어있어야 실행이 가능하다는 걸 알 수 있다.
(2) 이벤트 루프, 태스크 큐 관점에서 본 setTimeout의 동작
- 그럼, 다시 본론으로 돌아가서 생각해보자.
- 우리는
setTimeout
의delay
를 0ms로 지정해도, 지연이 발생하는 걸 확인했다. - 이벤트 루프와 태스크 큐 관점에서 보면,
setTimeout(fn, 0)
의 콜백 함수는 현재 콜 스택의 작업이 모두 완료된 후, 실행되기 때문이다. - MDN 공식 문서를 살펴보면, “delay를 생략하거나 0으로 지정하더라도 다음 이벤트 사이클에서 실행”된다고 설명되어 있다.(참고1, 참고2)
- 또한, WHATWG HTML 표준 문서에도 명시되어 있다.
- 해당 문서에서는 타이머 함수가 CPU 부하나 기타 작업 등 여러 요인에 의해 실행이 지연될 수 있다고 설명하고 있다.
그럼, 지연 시간이 0ms인
setTimeout
을 중첩 실행하면, 지연 시간이 늘어나는 이유는 뭘까?
- 앞서 지연이 0ms인
setTimeout
을 중첩 실행했을 때, 지연 시간이 증가하는 걸 보았다. - 그리고
delay
를 0ms로 설정해도, 콜 스택의 실행을 기다리기 위해 지연이 발생한다고 한다. - 그럼, 중첩 실행을 해도 6ms 내외의 지연된다고 생각했는데 왜 중첩 횟수가 많을수록 지연 시간이 늘어나는 걸까?
- 이 동작의 이유는 WHATWG HTML 표준 문서에서 알 수 있다.
- 위 이미지는 표준 문서 일부이다.
- 내용을 보면, “타이머는 중첩될 수 있으나, 5번 이상 중첩 호출했을 때 실행 간격이 4ms 이상”이 된다고 한다.
- 이는 0ms 타이머 호출이 계속될 경우, CPU가 과도하게 사용되어 사용자 인터랙션이나 화면 렌더링 등이 방해받는 상황을 막기 위해 마련된 규칙이라고 한다.
(3) 결국, 즉시 실행은 없다!
- 지금까지 얘기한 내용을 정리해보자. 우선, 자바스크립트는 싱글 스레드 환경에서 콜 스택, 태스크 큐, 그리고 이벤트 루프를 통해 비동기 작업을 순차적으로 실행한다.
- 그래서
setTimeout(fn, 0)
의 경우:- 현재 실행 중인 동기 작업이 모두 완료된 후,
- 태스크 큐에 있는 선행 작업들이 먼저 처리된 다음,
- 등록된 콜백 함수
fn
이 실행된다.
- 결국,
delay
값이 0ms이어도 즉시 실행되는 것이 아니라, “다음 이벤트 사이클”에서 실행되기에 미세한 지연이 발생한다. - 추가로, 중첩 호출 시에는 브라우저가 안전장치로 최소 4ms 이상의 지연을 강제하여, 과도한 CPU 사용이나 UI 응답성 저하를 방지한다.
3. 마치며…
이번 시간에는 0ms delay
를 지정한 setTimeout
이 왜 “즉시 실행”되지 않고, 지연이 발생하는지 살펴보았다. 정리하면 다음과 같은 포인트가 있었다.
- 자바스크립트의 실행 모델:
- 자바스크립트는 싱글 스레드 환경에서 동작하며,
- 콜 스택과 태스크 큐 그리고 이벤트 루프를 통해 순차적으로 작업을 처리한다.
- 이 메커니즘 때문에, 동기 코드가 모두 실행된 후에야 비동기 콜백이 실행된다.
- 0ms delay의 의미:
setTimeout(fn, 0)
은 “지연 없이 실행”하는 것이 아니라,- 현재 실행 중인 작업이 모두 끝난 후 다음 이벤트 사이클에 실행되도록 예약하는 역할이다.
- MDN과 WHATWG 표준 문서에서도
delay
를 생략하거나 0으로 지정한 경우, “다음 이벤트 사이클”에 실행된다고 명시하고 있다.
- 0ms delay를 중첩했을 때 최소 지연 시간:
- 또한,
setTimeout(fn, 0)
을 중첩 호출하면 브라우저는 안전장치로 4ms 이상의 최소 지연 시간을 강제한다. - 이는 반복 호출로 인한 CPU 과부하 및 UI 응답성 저하를 방지하기 위한 조치이다.
- 또한,
지금까지 살펴본 내용들이 실무에서 크게 문제가 되진 않을 것이다.
다만, setTimeout
의 동작을 알고 있느냐에 따라, 문제가 발생했을 때 이해를 바탕으로 대처할 수 있지 않을까 생각한다.
출처
'개발 기술 > 사소하지만 놓치기 쉬운 개발 지식' 카테고리의 다른 글
Base64 인코딩과 Path 방식의 장단점 및 성능 비교(feat.에셋 로드 방식) (0) | 2025.02.09 |
---|---|
[JS] Array 빌트인 함수는 정말 성능이 나쁠까? (forEach, map 등) (0) | 2025.01.24 |
[JS] 내장 프로토타입에 커스텀 함수를 등록하면 안되는 이유 (4) | 2024.12.20 |
[React] 반응형 데이터 연속 연산시 주의할 점(feat. Vue, React의 차이) (3) | 2024.12.09 |
웹사이트를 최적화시키는 3가지 기법: 코드 압축, 경량화, 난독화 (4) | 2024.11.18 |
댓글