41장 타이머
호출 스케줄링
- 함수를 명시적으로 호출하지 않고 일정 시간이 경과된 이후에 호출되도록 함수 호출을 예약하는 것
- 이때
타이머 함수
를 사용한다.
- 타이머 함수는 ECMA Script 사양에 정의된 빌트인 함수가 아니라, 브라우저 환경과 Node.js환경에서 모두 전역 객체의 메서드로서 타이머 함수를 제공한다. (⇒ 타이머 함수는
호스트 객체
이다.)
- 자바스크립트 엔진은 단 하나의 실행 컨텍스트를 갖기 때문에, 두 가지 이상의 태스크를 동시에 실행할 수 없다. (⇒
싱글 쓰레드로 동작
한다. )
- 이런 이유로 타이머 함수는
비동기 처리 방식으로 동작
한다.
setTimeout / clearTimeout
- 두번째 인수로 전달된 시간(ms)으로 단 한번만 동작하는 타이머를 생성한다.
⇒ 두번째 인자를 생략하면 기본값인 0이 들어간다.
- 타이머가 끝나면 첫번째 인수로 전달된 콜백 함수가 호출된다.
⇒ setTimeout의 함수의 콜백 함수는 두번째 인수로 전달받은 시간 이후 한번만 실행되도록 호출 스케줄링 된다.
- 만약 콜백 함수에 인수를 전달하고 싶다면, 세번째 이후에 적어 전달하면 된다.
- setTimemout 함수는 생성한 타이머를 식별할 수 있는 고유한 타이머 id를 반환한다.
- 반환한 타이머 id를 활용해 clearTimeout 함수에서 해당 타이머를 해제할 수 있다.
// 기본 형태 setTimeout(() => console.log('Hi'), 1000); // 콜백 함수에 인수를 전달하려는 경우 setTimeout(name => console.log(`Hi, I'm ${name}`, 1000, 'dongwoo'); // setTimeout을 변수에 할당하는 경우 const timerId = setTimeout(() => conosole.log('id test', 1000); // clearTimeout으로 타이머 해제 하기 clearTimeout(timerId);
setInterval / clearIneterval
- 두번째 인수로 전달된 시간(ms)으로 반복 동작하는 타이머를 생성한다.
⇒ 두번째 인자를 생략하면 기본값인 0이 들어간다.
- 타이머가 만료될 때마다 첫번째 인수로 전달받은 콜백함수가 호출된다. (타이머가 취소 될때까지 계속)
⇒ setInterval 함수의 콜백함수는 두번째 인수로 전달받은 시간이 경과할때마다 반복 실행되도록 호출 스케줄링이 일어난다.
- 만약 콜백 함수에 인수를 전달하고 싶다면, 세번째 이후에 적어 전달하면 된다.
- setInterval 함수는 생성한 타이머를 식별할 수 있는 고유한 타이머 id를 반환한다.
- 반환한 타이머 id를 활용해 clearInterval 함수에서 해당 타이머를 해제할 수 있다.
// 기본 형태 setInterval(() => console.log('Hi'), 1000); // 콜백 함수에 인수를 전달하려는 경우 setInterval(name => console.log(`Hi, I'm ${name}`, 1000, 'dongwoo'); const timeoutId = setInterval(() => { // 반복 실행이라 이렇게 콜백함수 내부에서도 해제가능 if(count++ === 5) clearInterval(timerId); , 1000}
디바운스와 쓰로틀
- 짧은 시간 간격에 연속해서 발생하는 이벤트들을 그룹화해서 과도한 이벤트 핸들로 호출 방지하는 프로그래밍 기법
ex) 짧은 기간 동안 많은 클릭, 짧은 기간 동안 많은 인풋
- 해당 기법들을 사용하는데 타이머 함수가 사용된다.
디바운스 (
Debounce)
- 짧은 시간 간격으로 이벤트가 연속해서 발생하면, 이벤트 핸들러를 호출하지 않다가 일정 시간이 경과한 이후에 이벤트 핸들러가
한 번만
호출되도록 한다. - 검색, 자동완성, 버튼 중복 클릭 방지 처리 등에 활용된다.
- 디바운스의 원리
- debounce 함수는 timerId를 기억하는 클로저를 반환한다.
- delay가 경과하기 이전에 이벤트가 발생하면, 이전 타이머를 취소하고 타이머 재설정
- 따라서 delay보다 짧은 간격으로 이벤트가 발생하면 callback은 호출되지 않는다.
- delay 보다 짧은 간격으로 input 이벤트가 발생하면 debounce의 콜백 함수는 호출되지 않다가 delay 동안 input 이벤트가 더 이상 발생하지 않으면 한 번만 호출됨
const $input = document.querySelector('input'); const $msg = document.querySelector('msg'); const debounce = (callback, delay) => { let timerId; return event => { if (timerId) clearTimeout(timerId); timerId = setTimeout(callback, delay, event); } } $input.oninput = debounce(e => { $msg.textContext = e.target.value; }, 300);

스로틀 (
Throttle)
- 짧은 시간 간격으로 이벤트가 연속으로 발생하더라도 일정 시간 간격으로 이벤트 핸들러가
최대 한번만
호출하도록 하는 것
⇒ 짧은 시간 동안 연속해서 발생하는 같은 이벤트를
그룹화
해서 일정 시간 단위로 이벤트 핸들러가 호출되도록 호출 주기를 만드는 것- 스로틀링의 원리
- throttle 함수는 timerId를 기억하는 클로저를 반환한다.
- delay가 경과하기 이전에 이벤트가 발생하면 아무것도 하지 않다가, delay가 경과했을 때, 이벤트가 발생하면 새로운 타이머를 재설정한다.
- delay 간격으로 callback이 호출된다.
const throttle = (callback, delay) => { let timerId; return event => { if (timerId) return; timerId = setTimeout(() => { callback(event); timerId = null; }, delay, event); } } document.addEventListener('scroll', throttle(() => { $throttleCount.textContext = ++throttleCount; }, 1000);

출처
42 비동기 프로그래밍
- 자바스크립트에서 함수를 호출하면 함수 코드가 평가되어 실행 컨텍스트를 만든다.
- 해당 실행 컨텍스트는 실행 컨텍스트 스택. 즉, 콜스택에 쌓이게 되어 함수 코드가 순서대로 실행된다.
- 함수 코드의 실행이 종료되면 해당 실행 컨텍스트가 스택에서 pop되어 제거되게 된다.
- 그런데 자바스크립트 엔진은
싱글 쓰레드
이다.단 하나의 실행 컨텍스트 스택을 갖는다
는 것이다. - 그렇게 되면 딜레이가 있는 경우
블로킹
이 발생할 수 있다. ex) sleep 함수 - 그러나 setTimeout은 딜레이가 있는데도 불구하고, 이후의 태스크를 블로킹 하지 않는다. (how?)
동기 처리
: 실행 중인 태스크가 종료될 때 까지 다음에 실행될 태스크 들이 대기하는 방식- 실행 순서가 보장된다.
- 앞선 태스크가 종료할 때까지 이후 태스크들이 블로킹 되는 단점이 있다.
비동기 처리
: 실행 중인 태스크가 종료되지 않았더라도 다음 태스크를 곧바로 실행하는 방식- 블로킹이 되지 않지만, 실행 순서는 보장되지 않는다.
이벤트 루프와 태스크 큐
- 자바스크립트는 싱글 쓰레드인데, 브라우저가 동작하는 것을 보면 많은 태스크가 동시에 처리되는 것처럼 보인다.
이벤트 루프
: 자바스크립트의 동시성을 지원하는 것

- 왼쪽이 자바스크립트 엔진
콜스택
: 실행 컨텍스트(변수 식별자 저장, 스코프나 this 관리)들이 쌓임, 코드 실행 순서 관리메모리 힙
: Object 타입들의 데이터가 저장된다. (메모리 할당이 일어나는 곳)- 자바스크립트 엔진은 단순히 태스크가 요청되면 콜스택을 통해 요청된 작업을 순차적으로 진행한다.
- 비동기 처리에서 소스코드의 평가와 실행을 제외한 모든 처리는 자바스크립트를 구동하는 환경인 브라우저나, Node.js가 담당한다.
⇒ (그래서 const Array ⇒ Array.push, Array.pop값을 바꿀 수 있다.)
태스크(콜백) 큐
- Web APIs와 같은 비동기 함수들의 콜백 함수들은 TaskQueue에 순서대로 쌓임. (브라우저가)
- 태스크 큐와는 별도로 프로미스의 후속 처리 메서드의 콜백 함수가 일시적으로 보관되는 마이크로 태스크 큐가 있다. (마이크로 태스크 큐가 태스크 큐보다 우선순위가 높다.)
MacroTaskQueue
(Task Queue)- setTimeout, setInterval, EventDispatch 등의 콜백함수가 들어간다.
- 이벤트 루프는 MacroTaskQueue에 있는 task를 하나만 꺼내서 실행한다.
MicroTaskQueue
- Promise의 then, Mutation Observer의 핸들러가 들어간다.
- 이벤트 루프는 MicroTaskQueue가 빌때까지 task를 꺼내서 실행한다. 1 ← 2
- task를 처리하는 도중에 queue에 새로운 task가 들어와도 다음 루프로 미루지 않고 쭉 실행
- 중간에 어떤 코드를 실행시키지 않는다.
우선순위가 가장 높음
⇒ JS 엔진의 CallStack 이 비자마자 제일 먼저 비워짐AnimationFramesQueue
- requestAnimationFrames으로 등록된 콜백 함수들이 해당 큐에 들어간다
- repaint 직전에 queue에 있던 task들을 전부 처리한다.
- animation에 사용하면 frame drop을 최적화 할 수 있다.
- 주의할 점
- 첫째. 비동기 작업으로 등록되는 작업은 task와 microtask. 그리고 animationFrame 작업으로 구분된다.
- 둘째. microtask는 task보다 먼저 작업이 처리된다. (브라우저 마다 다름)
- 셋째. microtask가 처리된 이후 requestAnimationFrame이 호출되고 이후 브라우저 랜더링이 발생한다.
이벤트 루프
- 콜 스택에 현재 실행중인 실행 컨텍스트가 있는지, 태스크 큐에 대기중인 콜백 함수가 있는지 계속해서 확인한다.
- 콜스택이 비어 있고, 태스크 큐에 대기중인 함수가 있으면 하나씩 콜 스택으로 이동시킨다.
예시
console.log("script start"); // 실행 콜스택 -> pop // MacroTaskQueue setTimeout(function() { // console.log("setTimeout"); }, 1000); // MicroTaskQueue Promise.resolve().then(function() { console.log("promise1"); }).then(function() { console.log("promise2"); }); // AnimationFrameQueue requestAnimationFrame(function { console.log("requestAnimationFrame"); }) console.log("script end");
순서
- console.log (script start) 처리된다.
- setTimeout 작업이 stack에 등록되고, Web API에게 setTimeout을 요청한다. 이때 setTimeout의 callback 함수를 함께 전달한다. 요청 이후 stack에 있는 setTimeout 작업은 제거된다.
- 브라우저는 setTimeout 작업 이 끝난 후(
delay 이후
), callback 함수가task Queue
에 등록된다.
- Promise 작업이 stack에 등록되고, Web API에게 Promise 작업을 요청한다. 이때 Promise.then의 callback 함수를 함께 전달한다. 요청 이후 stack에 있는 Promise 작업은 제거된다.
- 브라우저는 Promise 작업이 완료되면
Promise.then
의 callback 함수를microtask queue
에 등록한다.
- requestAnimation 작업이 stack에 등록되고, 브라우저 에게
reuqestAnimation
을 요청한다. 이때 requestAnimation의 callback 함수를 함께 전달한다. 요청 이후 stack에 있는 requestAnimation 작업은 제거된다.
- 브라우저는 requestAnimation의 callback 함수를 animation frame에 등록

- console.log('script end')가 처리된다. (callStack이 비워짐)
MicroTaskQueue
부터 콜스택으로 순서대로 이동한다.- 해당 then이 실행되고 (콜 스택에서 빠지고), 콜백함수에 또 하나의 then이 있다. 다시 해당 Promise.then의 콜백 함수가 MicroTaskQueue에 쌓인다.
- MicroTaskQueue는 해당 큐에 있는 모든 task들을 처리해야 끝나므로 두번째 then이 그 다음에 콜스택으로 push 되어 실행된다.
AnimationFrameQueue
가 콜스택으로 순서대로 이동한다.- 콜백 함수가 실행되고 콜스택에서 빠진다.
- 이후 브라우저는 랜더링 작업을 하여 UI를 업데이트한다.
MacroTaskQueue(TaskQueue)
의 함수들이 콜스택으로 순서대로 이동해서 실행되고 끝난다~!
결과
script start script end promise1 promise2 requestAnimationFrame setTimeout
출처