자바스크립트 엔진은 `싱글 스레드`이기 때문에, 명시적으로 함수를 호출하면 동기적으로 실행되어 해당 함수가 종료될 때까지 다른 작업을 수행할 수 없다. 하지만, 일정한 시간이 경과된 후에 실행되도록 하는 타이머 함수를 사용하면 비동기적으로 함수를 실행할 수 있다. 이를 호출 스케줄링이라고 한다.
자바스크립트는 타이머를 생성할 수 있는 타이머 함수 `setTimeout`과 `setInterval`, 타이머를 제거할 수 있는 타이머 함수 `clearTimeout`과 `clearInterval`을 제공한다. 타이머 함수는 ECMAScript 사양에 정의된 빌트인 함수가 아니라 브라우저 환경과 Node.js 환경에서 전역 객체의 메서드로서 타이머 함수를 제공한다. 즉, 타이머 함수는 호스트 객체(실행 환경에서 제공하는 객체)이다.
setTimeout
setTimeout은 두 번째 인수로 전달받은 시간(ms, 1/1000초)으로 단 한 번만 동작하는 타이머를 생성한다. 이후 타이머 시간이 경과되면 첫 번째 인수로 전달받은 콜백 함수가 호출된다. setTimeout은 고유한 타이머 ID값을 반환한다.
const timeoutId = setTimeout(() => console.log('Hi'), 1000); // 1초 뒤에 콜백 함수 호출
setInterval
setInterval은 setTimeout과는 달리 단 한 번만 동작하는 것이 아니라 타이머가 만료될 때마다 반복하여 콜백 함수를 호출한다. setInterval 역시 고유한 타이머 ID값을 반환한다.
let count = 1;
const timeoutId = setInterval(() => {
console.log(count); // 1 2 3 4 5
if(count++ === 5) clearInterval(timeoutId); // 카운트 5가 되면 타이머 취소
}, 1000);
이러한 타이머 함수들은 비동기적으로 동작한다. 자바스크립트에서 비동기 처리 방식을 알기 위해서는 이벤트 루프와 태스크 큐에 대해서 알아야 한다.
자바스크립트에서의 비동기 처리
자바스크립트에서는 이벤트 핸들러, 타이머 함수, HTTP 요청이 비동기 처리 방식으로 동작한다. 자바스크립트는 싱글 스레드인데 어떻게 비동기적으로 처리가 가능한 것일까? 그 이유는 브라우저에서 제공하는 `이벤트 루프(Event Loop)`를 통해 자바스크립트의 동시성(Concurrency : 여러 개의 작업이 동시에 처리되는 것처럼 보임)을 지원하기 때문이다.
- 동기적 처리 : 현재 실행중인 작업(태스크)가 종료되어야만 다음 작업을 실행하는 방식 ➡️ 블로킹(Blocking)
- 비동기적 처리 : 현재 실행중인 작업(태스크)가 종료되지 않아도 다음 작업을 실행하는 방식 ➡️ 논-블로킹(Non-Blocking)
#상황 1 : 타이머 함수
console.log("시작")
setTimeout(() => {
console.log("타이머 완료")
}, 1000)
console.log("끝")
- 전역 실행 컨텍스트가 생성되고 `시작` 콜 스택에 push되어 실행 후 pop
- `setTimeout`이 콜 스택에 push되어 실행됨
- 1초 동안 대기 큐에서 대기 후 `Task Queue`에 등록
- `끝` 콜스택에 push되어 실행 후 pop
- 이벤트 루프가 콜 스택이 비었는지 확인 후 태스크 큐에서 순서대로 실행
#상황 2 : 이벤트 핸들러
console.log("이벤트 리스너 등록")
document.body.addEventListener("click", () => {
console.log("클릭 이벤트 실행")
})
console.log("다른 작업 수행 중...")
- 전역 실행 컨텍스트가 생성되고 `이벤트 리스너 등록` 콜 스택에 push되어 실행 후 pop
- 이벤트 리스너 목록에 이벤트 핸들러 등록(실제 이벤트가 발생했을 때 `Task Queue`에 콜백 함수 등록)
- `다른 작업 수행 중...` 콜 스택에 push되어 실행 후 pop
- 이벤트 루프가 콜 스택이 비었는지 확인 후 태스크 큐에서 순서대로 실행
#상황 3 : HTTP 요청
console.log("시작")
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then(() => console.log("fetch 완료"))
console.log("끝")
- 전역 실행 컨텍스트가 생성되고 `시작` 콜 스택에 push되어 실행 후 pop
- `fetch` 콜 스택에 push되어 HTTP 요청을 Web API에게 위임 후 pop
- `끝` 콜 스택에 push되어 실행 후 pop
- 응답이 오면 `MicroTask Queue`에 등록
- 이벤트 루프가 콜 스택이 비었는지 확인 후 태스크 큐보다 먼저 실행
타이머가 정확한 시간에 동작하지 않는 이유?
애플리케이션이 복잡하고 커질수록 비동기 처리 동작이 원활하지 않을 수 있다. 타이머 함수는 지정된 시간 후에 콜백 함수를 태스크 큐에 등록하는 역할을 한다. 그렇기 때문에 콜 스택이 가득 차서 비워지지 않으면, 그만큼 태스크 큐와 마이크로 태스크 큐의 실행이 지연될 수밖에 없다.
타이머를 정확한 시간에 동작하도록 하는 방법은 없을까?
자바스크립트 비동기 처리 모델에서 완벽하게 정확한 시간에 동작하도록 하는 것은 어려움이 있다. 그럼에도 불구하고 보정을 통해 최대한 비슷한 시간에 동작하도록 할 수 있다.
1. requestAnimationFrame 사용 (UI 관련 동작)
브라우저에서 화면 갱신을 위한 최적화된 메서드인 requestAnimationFrame을 사용하면, 타이머가 정확한 시간에 실행되지는 않지만, 다음 화면 렌더링 주기에 맞춰 실행되므로 UI 동작에 더 정확한 타이밍을 제공한다. 단, requestAnimationFrame은 화면 렌더링과 관련된 작업에만 유용하다.
2. 타이머 중첩으로 오차 보정
setTimeout이나 setInterval이 정확하지 않은 이유는 지연 시간이 누적되기 때문이다. 예를 들어, setTimeout으로 1초 후 실행을 예약한 후, 해당 타이머가 실행되기 전에 여러 작업이 대기 큐에 쌓이면 타이머 실행 시간이 밀린다. 이를 보정하기 위해, 타이머가 실행된 후에 또 다시 타이머를 예약하는 방식으로 조금 더 정확하게 실행되도록 할 수 있다.
function preciseTimeout(callback, delay) {
const start = Date.now();
function checkTime() {
const elapsed = Date.now() - start;
const remaining = delay - elapsed;
if (remaining <= 0) {
callback();
} else {
setTimeout(checkTime, remaining);
}
}
setTimeout(checkTime, delay);
}
preciseTimeout(() => {
console.log("정확한 시간에 실행");
}, 1000);
3. Web Workers 사용
JavaScript의 메인 스레드와 별도로 작업을 처리할 수 있는 Web Worker를 사용하면, 주 스레드의 부하를 줄이고 더 정확한 타이밍으로 작업을 예약할 수 있다. 그러나, Web Worker는 UI와 직접적으로 상호작용할 수 없기 때문에 UI 관련 작업은 추가적인 통신이 필요하다.
4. performance.now()와 비교
더 높은 정확도를 요구하는 경우, performance.now()를 사용하여 더 정밀한 시간을 측정할 수 있다. performance.now()는 밀리초보다 더 작은 단위(마이크로초 단위)로 시간을 제공한다.
const targetTime = performance.now() + 1000; // 1초 후
function preciseTimer() {
if (performance.now() >= targetTime) {
console.log("정확한 시간에 실행");
} else {
requestAnimationFrame(preciseTimer);
}
}
requestAnimationFrame(preciseTimer);
'프론트엔드 > JavaScript' 카테고리의 다른 글
[모던 자바스크립트 Deep Dive] 10장 - 객체 (2) | 2024.12.17 |
---|---|
[모던 자바스크립트 Deep Dive] 08장 - 제어문 (3) | 2024.12.09 |
견고한 JS 소프트웨어 만들기(1) (1) | 2024.12.03 |
[모던 자바스크립트 Deep Dive] 07장 - 연산자 (3) | 2024.11.19 |
[모던 자바스크립트 Deep Dive] 06장 - 데이터 타입 (2) | 2024.11.18 |
자바스크립트 엔진은 싱글 스레드
이기 때문에, 명시적으로 함수를 호출하면 동기적으로 실행되어 해당 함수가 종료될 때까지 다른 작업을 수행할 수 없다. 하지만, 일정한 시간이 경과된 후에 실행되도록 하는 타이머 함수를 사용하면 비동기적으로 함수를 실행할 수 있다. 이를 호출 스케줄링이라고 한다.
자바스크립트는 타이머를 생성할 수 있는 타이머 함수 setTimeout
과 setInterval
, 타이머를 제거할 수 있는 타이머 함수 clearTimeout
과 clearInterval
을 제공한다. 타이머 함수는 ECMAScript 사양에 정의된 빌트인 함수가 아니라 브라우저 환경과 Node.js 환경에서 전역 객체의 메서드로서 타이머 함수를 제공한다. 즉, 타이머 함수는 호스트 객체(실행 환경에서 제공하는 객체)이다.
setTimeout
setTimeout은 두 번째 인수로 전달받은 시간(ms, 1/1000초)으로 단 한 번만 동작하는 타이머를 생성한다. 이후 타이머 시간이 경과되면 첫 번째 인수로 전달받은 콜백 함수가 호출된다. setTimeout은 고유한 타이머 ID값을 반환한다.
const timeoutId = setTimeout(() => console.log('Hi'), 1000); // 1초 뒤에 콜백 함수 호출
setInterval
setInterval은 setTimeout과는 달리 단 한 번만 동작하는 것이 아니라 타이머가 만료될 때마다 반복하여 콜백 함수를 호출한다. setInterval 역시 고유한 타이머 ID값을 반환한다.
let count = 1;
const timeoutId = setInterval(() => {
console.log(count); // 1 2 3 4 5
if(count++ === 5) clearInterval(timeoutId); // 카운트 5가 되면 타이머 취소
}, 1000);
이러한 타이머 함수들은 비동기적으로 동작한다. 자바스크립트에서 비동기 처리 방식을 알기 위해서는 이벤트 루프와 태스크 큐에 대해서 알아야 한다.
자바스크립트에서의 비동기 처리
자바스크립트에서는 이벤트 핸들러, 타이머 함수, HTTP 요청이 비동기 처리 방식으로 동작한다. 자바스크립트는 싱글 스레드인데 어떻게 비동기적으로 처리가 가능한 것일까? 그 이유는 브라우저에서 제공하는 이벤트 루프(Event Loop)
를 통해 자바스크립트의 동시성(Concurrency : 여러 개의 작업이 동시에 처리되는 것처럼 보임)을 지원하기 때문이다.
- 동기적 처리 : 현재 실행중인 작업(태스크)가 종료되어야만 다음 작업을 실행하는 방식 ➡️ 블로킹(Blocking)
- 비동기적 처리 : 현재 실행중인 작업(태스크)가 종료되지 않아도 다음 작업을 실행하는 방식 ➡️ 논-블로킹(Non-Blocking)
#상황 1 : 타이머 함수
console.log("시작")
setTimeout(() => {
console.log("타이머 완료")
}, 1000)
console.log("끝")
- 전역 실행 컨텍스트가 생성되고
시작
콜 스택에 push되어 실행 후 pop setTimeout
이 콜 스택에 push되어 실행됨- 1초 동안 대기 큐에서 대기 후
Task Queue
에 등록 끝
콜스택에 push되어 실행 후 pop- 이벤트 루프가 콜 스택이 비었는지 확인 후 태스크 큐에서 순서대로 실행
#상황 2 : 이벤트 핸들러
console.log("이벤트 리스너 등록")
document.body.addEventListener("click", () => {
console.log("클릭 이벤트 실행")
})
console.log("다른 작업 수행 중...")
- 전역 실행 컨텍스트가 생성되고
이벤트 리스너 등록
콜 스택에 push되어 실행 후 pop - 이벤트 리스너 목록에 이벤트 핸들러 등록(실제 이벤트가 발생했을 때
Task Queue
에 콜백 함수 등록) 다른 작업 수행 중...
콜 스택에 push되어 실행 후 pop- 이벤트 루프가 콜 스택이 비었는지 확인 후 태스크 큐에서 순서대로 실행
#상황 3 : HTTP 요청
console.log("시작")
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then(() => console.log("fetch 완료"))
console.log("끝")
- 전역 실행 컨텍스트가 생성되고
시작
콜 스택에 push되어 실행 후 pop fetch
콜 스택에 push되어 HTTP 요청을 Web API에게 위임 후 pop끝
콜 스택에 push되어 실행 후 pop- 응답이 오면
MicroTask Queue
에 등록 - 이벤트 루프가 콜 스택이 비었는지 확인 후 태스크 큐보다 먼저 실행
타이머가 정확한 시간에 동작하지 않는 이유?
애플리케이션이 복잡하고 커질수록 비동기 처리 동작이 원활하지 않을 수 있다. 타이머 함수는 지정된 시간 후에 콜백 함수를 태스크 큐에 등록하는 역할을 한다. 그렇기 때문에 콜 스택이 가득 차서 비워지지 않으면, 그만큼 태스크 큐와 마이크로 태스크 큐의 실행이 지연될 수밖에 없다.
타이머를 정확한 시간에 동작하도록 하는 방법은 없을까?
자바스크립트 비동기 처리 모델에서 완벽하게 정확한 시간에 동작하도록 하는 것은 어려움이 있다. 그럼에도 불구하고 보정을 통해 최대한 비슷한 시간에 동작하도록 할 수 있다.
1. requestAnimationFrame 사용 (UI 관련 동작)
브라우저에서 화면 갱신을 위한 최적화된 메서드인 requestAnimationFrame을 사용하면, 타이머가 정확한 시간에 실행되지는 않지만, 다음 화면 렌더링 주기에 맞춰 실행되므로 UI 동작에 더 정확한 타이밍을 제공한다. 단, requestAnimationFrame은 화면 렌더링과 관련된 작업에만 유용하다.
2. 타이머 중첩으로 오차 보정
setTimeout이나 setInterval이 정확하지 않은 이유는 지연 시간이 누적되기 때문이다. 예를 들어, setTimeout으로 1초 후 실행을 예약한 후, 해당 타이머가 실행되기 전에 여러 작업이 대기 큐에 쌓이면 타이머 실행 시간이 밀린다. 이를 보정하기 위해, 타이머가 실행된 후에 또 다시 타이머를 예약하는 방식으로 조금 더 정확하게 실행되도록 할 수 있다.
function preciseTimeout(callback, delay) {
const start = Date.now();
function checkTime() {
const elapsed = Date.now() - start;
const remaining = delay - elapsed;
if (remaining <= 0) {
callback();
} else {
setTimeout(checkTime, remaining);
}
}
setTimeout(checkTime, delay);
}
preciseTimeout(() => {
console.log("정확한 시간에 실행");
}, 1000);
3. Web Workers 사용
JavaScript의 메인 스레드와 별도로 작업을 처리할 수 있는 Web Worker를 사용하면, 주 스레드의 부하를 줄이고 더 정확한 타이밍으로 작업을 예약할 수 있다. 그러나, Web Worker는 UI와 직접적으로 상호작용할 수 없기 때문에 UI 관련 작업은 추가적인 통신이 필요하다.
4. performance.now()와 비교
더 높은 정확도를 요구하는 경우, performance.now()를 사용하여 더 정밀한 시간을 측정할 수 있다. performance.now()는 밀리초보다 더 작은 단위(마이크로초 단위)로 시간을 제공한다.
const targetTime = performance.now() + 1000; // 1초 후
function preciseTimer() {
if (performance.now() >= targetTime) {
console.log("정확한 시간에 실행");
} else {
requestAnimationFrame(preciseTimer);
}
}
requestAnimationFrame(preciseTimer);
'프론트엔드 > JavaScript' 카테고리의 다른 글
[모던 자바스크립트 Deep Dive] 10장 - 객체 (2) | 2024.12.17 |
---|---|
[모던 자바스크립트 Deep Dive] 08장 - 제어문 (3) | 2024.12.09 |
견고한 JS 소프트웨어 만들기(1) (1) | 2024.12.03 |
[모던 자바스크립트 Deep Dive] 07장 - 연산자 (3) | 2024.11.19 |
[모던 자바스크립트 Deep Dive] 06장 - 데이터 타입 (2) | 2024.11.18 |