일정 시간이 지난 후에 원하는 함수를 예약 실행(호출)할 수 있게 하는 것을 '호출 스케줄링(scheduling a call)'이라고 한다.
[호출 스케줄링을 구현하는 방법]
// 문법
let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)
func|code
실행하고자 하는 코드로, 함수 또는 문자열 형태이다. 대개는 이 자리에 함수가 들어가는데, 하위 호환성을 위해 문자열도 받을 수 있게 해놓았지만 추천하진 않는다.
delay
실행 전 대기 시간으로, 단위는 밀리초(millisecond, 1000밀리초 = 1초)이며 기본값은 0이다.
arg1, arg2…
함수에 전달할 인수들로, IE9 이하에선 지원 X
function want() {
alert('졸리다.');
}
setTimeout(want, 1000); // 1초 뒤에 호출
function sayHi(who, phrase) {
alert( who + ' 님, ' + phrase );
}
setTimeout(sayHi, 1000, "홍길동", "안녕하세요."); // 홍길동 님, 안녕하세요.
setTimeout("alert('안녕하세요.')", 1000);
setTimeout(() => alert('안녕하세요.'), 1000);
setTimeout의 첫 번째 인수가 문자열이면 자바스크립트는 이 문자열을 이용해 함수를 만든다. (비추)
=> 문자열을 사용하지말고, 익명 화살표 함수를 사용하자.
🌱 함수를 실행하지 말고 넘겨야한다.
setTimeout(want(), 1000);
위의 코드와 같이 setTimeout에 함수를 넘길 때, 함수 뒤에 ()를 붙이면 안된다. setTimeout은 함수의 참조 값을 받도록 정의되어 있는데 want()를 인수로 전달하면 함수 실행 결과가 전달되기 때문이다. want()엔 반환문이 없기 때문에 호출 결과가 undefined가 되어, 스케줄링할 대상을 찾지 못해, 원하는 대로 코드가 동작하지 않는다.
setTimeout을 호출하면 '타이머 식별자(timer identifier)'가 반환된다. 스케줄링을 취소하고 싶을 땐 이 식별자를 사용하면 된다.
// 문법
let timerId = setTimeout(...);
clearTimeout(timerId);
브라우저 환경에선 타이머 식별자가 숫자인데, 다른 호스트 환경에선 타이머 식별자가 숫자형 이외의 자료형일 수 있다. 참고로 Node.js에서 setTimeout을 실행하면 타이머 객체가 반환된다.
let timerId = setTimeout(() => alert("아무런 일도 일어나지 않습니다."), 1000);
alert(timerId);
clearTimeout(timerId);
alert(timerId); // 위 타이머 식별자와 동일함 (취소 후에도 식별자의 값은 null이 되지 않는다.)
// 문법
let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)
setTimeout과 동일한 문법을 사용하지만, setTimeout이 함수를 단 한 번만 실행하는 것과 달리 setInterval은 함수를 주기적으로 실행하게 만든다.
함수 호출을 중단할 때는 clearInterval(timerId)을 사용.
let timerId = setInterval(() => alert('야'), 2000);
setTimeout(() => { clearInterval(timerId); alert('호'); }, 5000);
🌱 alret창과 관계없이 타이머는 멈추지 않는다.
Chrome과 Firefox를 포함한 대부분의 브라우저는 alert/confirm/prompt 창이 떠 있는 동안에도 내부 타이머를 멈추지 않는다.
위 예시를 실행하고 첫 번째 alert 창이 떴을 때 몇 초간 기다렸다가 창을 닫으면, 두 번째 alert 창이 바로 나타나는 것을 보고 이를 확인할 수 있다. 이런 이유로 얼럿 창은 명시한 지연 시간인 2초보다 더 짧은 간격으로 뜨게 된다.
무언가를 일정 간격을 두고 실행하는 방법에는 크게 2가지가 있다.
하나는 setInterval을 이용하는 방법이고, 다른 하나는 중첩 setTimeout을 이용하는 방법이다.
let timerId = setTimeout(function hi() {
alert('안녕');
timerId = setTimeout(hi, 2000); // (*)
}, 2000);
첫 번째 줄의 setTimeout은 (*)로 표시한 줄의 실행이 종료되면 다음 호출을 스케줄링한다.
중첩 setTimeout을 이용하는 방법은 setInterval을 사용하는 방법보다 유연하다. 호출 결과에 따라 다음 호출을 원하는 방식으로 조정해 스케줄링 할 수 있기 때문!!
또한 중첩 setTimeout을 이용하는 방법은 지연 간격을 보장하지만 setInterval은 이를 보장하지 않는다.
let i = 1;
setInterval(function() {
func(i++);
}, 100);
let j = 1;
setTimeout(function run() {
func(j++);
setTimeout(run, 100);
}, 100);
setInterval을 사용하면 func호출 사이의 지연 간격이 실제 명시한 간격(100ms)보다 짧아진다. 이는 func을 실행하는 데 ‘소모되는’ 시간도 지연 간격에 포함시키기 때문
그래서 만약 func을 실행하는 데 걸리는 시간이 명시한 지연 간격보다 길어진다면, 엔진이 func의 실행이 종료될 때까지 기다려준다. func의 실행이 종료되면 엔진은 스케줄러를 확인하고, 지연 시간이 지났으면 다음 호출을 바로 시작한다. 따라서 함수 호출에 걸리는 시간이 매번 delay 밀리초보다 길면, 모든 함수가 쉼 없이 계속 연속 호출된다.
setTimeout을 사용하면 명시한 지연(여기서는 100ms)이 보장된다. 이전 함수의 실행이 종료된 이후에 다음 함수 호출에 대한 계획이 세워지기 때문이다.
setTimeout(func, 0)이나 setTimeout(func)을 사용하면 setTimeout의 대기 시간을 0으로 설정할 수 있다.
이렇게 대기 시간을 0으로 설정하면 func을 ‘가능한 한’ 빨리 실행할 수 있다. 다만, 이때 스케줄러는 현재 실행 중인 스크립트의 처리가 종료된 이후에 스케줄링한 함수를 실행합니다.
이런 특징을 이용하면 현재 스크립트의 실행이 종료된 ‘직후에’ 원하는 함수가 실행될 수 있게 할 수 있다.
setTimeout(() => alert("World"));
alert("Hello");
🌱 브라우저 환경에서 실제 대기 시간은 0이 아니다.
브라우저는 HTML5 표준에서 정한 중첩 타이머 실행 간격 관련 제약을 준수한다. 해당 표준엔 "다섯 번째 중첩 타이머 이후엔 대기 시간을 최소 4밀리초 이상으로 강제해야 한다."라는 제약이 명시되어있다.let start = Date.now(); let times = []; setTimeout(function run() { times.push(Date.now() - start); // 이전 호출이 끝난 시점과 현재 호출이 시작된 시점의 시차를 기록 if (start + 100 < Date.now()) alert(times); // 지연 간격이 100ms를 넘어가면, array를 얼럿창에 띄워줌 else setTimeout(run); // 지연 간격이 100ms를 넘어가지 않으면 재스케줄링함 }); // 출력창 예시: // 1,1,1,1,9,15,20,24,30,35,40,45,50,55,59,64,70,75,80,85,90,95,100
이런 제약은 setTimeout뿐만 아니라 setInterval에도 적용된다. setInterval(f)도 처음 몇 번은 함수 f를 지연 없이 실행하지만, 나중엔 지연 간격을 4밀리초 이상으로 늘려버린다. 하위호환성을 위해 유지중이며, 서버 측에서는 제약이 없다.(브라우저 한정)
setInterval(func, delay, ...args)과 setTimeout(func, delay, ...args)은 delay밀리초 후에 func을 규칙적으로, 또는 한번 실행하도록 해준다. setInterval·setTimeout을 호출하고 반환받은 값을 clearInterval·clearTimeout에 넘겨주면 스케줄링을 취소할 수 있다.
중첩 setTimeout을 사용하면 setInterval을 사용한 것보다 유연하게 코드를 작성할 수 있다. (지연 간격 보장)
대기 시간이 0인 setTimeout(setTimeout(func, 0) 혹은 setTimeout(func))을 사용하면 ‘현재 스크립트의 실행이 완료된 후 가능한 한 빠르게’ 원하는 함수를 호출할 수 있다.
참고자료