본문 바로가기

모던 자바스크립트

6.8 setTimeout과 setInterval을 이용한 호출 스케줄링

📢 호출 스케줄링(scheduling a call) : 일정 시간이 지난 후에 원하는 함수를 예약 실행(호출) 할 수있게 하는 것

 

호출 스케줄링 구현 방법

◾️ setTimeout을 이용해 일정 시간이 지난 후에 함수를 실행하는 방법

◾️ setInterval을 이용해 일정 시간 간격을 두고 함수를 실행하는 방법

 

setTimeout

◾️ setTimeout 문법

let timerId = setTimeout(func|code, [delay], [arg1], [arg2], ...)

◾️ 매개변수

  • func|code : 실행하고자 하는 코드. (함수 또는 문자열 형태. 대개 함수가 들어간다)
  • delay : 실행 전 대기 시간. 단위는 밀리초(millisecond, 1000밀리초 = 1초) 기본값 = 0
  • arg1, arg2 ... : 함수에 전달할 인수
function sayHi() {
  console.log("Hi");
}

setTimeout(sayHi, 1000); // 1초후 Hi가 출력

◾️ 함수에 인수를 넘겨줄 수도 있다. 

function sayHi(who, phrase) {
  console.log(`${who}님 ${phrase}`);
}

setTimeout(sayHi, 1000, 'suzu', 'Hi'); // 1초후 suzu님 Hi가 출력

◾️ setTimeout의 첫 번째 인수가 문자열이면 자바스크립트는 이 문자열을 이용해 함수를 만든다. 

setTimeout("alert('Hi')", 1000);

◾️ 문자열 보다는 익명 화살표 함수를 사용하는 편이 좋다. 

setTimeout(() => alert('Hi!'), 1000);

 

⚠️ 함수를 실행하지 말고 넘겨야한다. 

❌ setTimeout(sayHi(), 1000);

setTimeout은 함수의 참조 값을 받도록 정의되어 있는데 sayHi()를 인수로 전달하면 함수 실행 결과가 전달되어 버린다. 그런데 sayHi()에는 return 값이 존재하지 않기 때문에 호출 결과는 undefined가 된다. 따라서 setTimeout은 스케줄링할 대상을 찾지 못해, 원하는 대로 코드가 동작하지 않는다.  

 

clearTimeout으로 스케줄링 취소하기 

◾️ setTimeout을 호출하면 타이머 식별자(timer identifier)가 반환된다. 스케줄링을 취소하고 싶을 땐 이 식별자를 사용하면 된다. 

◾️ 스케줄링 취소하기

let timerId = setTimeout(...);
clearTimeout(timerId);

◾️ 함수 실행을 계획해 놓았다가 중간에 마음이 바뀌어 계획해 놓았던 것을 취소한 상황

let timerId = setTimeout(() => alert('Nothing'), 1000);
console.log(timerId); //타이머 식별자

clearTimeout(timerId);
console.log(timerId); // 위 타이머 식별자와 동일 (취소 후에도 식별자의 값은 null이 되지 않는다)

◾️ 브라우저 환경에서 타이머 식별자는 숫자이다. 호스트 환경마다 다른 자료형일 수 있다.

◾️ Node.js에서 setTimeout을 실행하면 타이머 객체가 반환된다. 

 

 

setInterval

◾️ setInterval 문법 (setTimeout과 동일한 문법과 인수)

let timerId = setInterval(func|code, [delay], [arg1], [arg2], ...)

◾️ setInterval은 함수를 주기적으로 실행하게 만든다. 

◾️ 함수 호출을 중단하려면 clearInterval(timerId)를 사용한다. 

// 2초 간격으로 출력
let timerId = setInterval(() => alert('2초'), 2000);

// 5초 후에 정지
setTimeout(() => { clearInterval(timerId); alert('정지'); }, 5000);
⚠️ alert창이 떠 있더라도 타이머는 멈추지 않는다. 

Chrome과 Firefox를 포함한 대부분의 브라우저는 alert/confirm/prompt 창이 떠 있는 동안에도 내부 타이머를 멈추지 않는다. 

 

 

중첩 setTimeout

◾️ 무언가를 일정 간격을 두고 실행하는 방법 2가지 

  1. setInterval을 이용하는 방법
  2. 중첩 setTimeout을 이용하는 방법
// setInterval을 이용하지 않고 중첩 setTimeout을 사용함
// let timerId = setInterval(() => alert('2초'), 2000);

let timerId = setTimeout(function tick() {
  alert('2초');
  timerId = setTimeout(tick, 2000); // (*)
}, 2000);

◾️ 첫번째 setTimeout은 (*)로 표시한 줄의 실행이 종료되면 다음 호출을 스케줄링 한다. 

◾️ 중첩 setTimeout을 이용하는 방법은 setInterval을 사용하는 방법보다 유연하다.

◾️ 호출 결과에 따라 다음 호출을 원하는 방식으로 조정해 스케줄링 할 수 있다. 

 

◾️ 5초 간격으로 서버에 요청을 보내 데이터를 얻는다고 할 때, 서버가 과부하 상태라면 요청 간격을 10초, 20초, 40초 등으로 증가시키는 코드

let delay = 5000;

let timerId = setTimeout(function request() {
  // 요청 보내기
  
  if (서버 과부하로 인한 요청 실패) {
  	// 요청간격을 늘린다. 
    delay *= 2;
  }
  
  timerId = setTimeout(request, delay);
  
}, delay);

◾️ CPU 소모가 많은 작업을 주기적으로 실행하는 경우에도 setTimeout을 재귀 실행하는 방법이 유용하다. 

◾️ 작업에 걸리는 시간에 따라 다음 작업을 유동적으로 계획할 수 있다. 

◾️ 중첩 setTimeout을 이용하는 방법은 지연 간격을 보장하지만 setInterval은 이를 보장하지 않는다. 

◾️ setInterval을 이용한 예시

  • 내부 스케줄러가 func(i++)를 100밀리초마다 실행한다. 
  • setInterval을 사용하면 func 호출 사이의 지연 간격이 실제 명시한 간격(100ms)보다 짧아진다.
  • func를 실행하는 데 소모되는 시간도 지연 간격에 포함시키기 때문
  • 엔진이 func의 실행이 종료될 때까지 기다려준다. func의 실행이 종료되면 엔진은 스케줄러를 확인하고, 지연 시간이 지났으면 다음 호출을 바로 시작한다. 
  • 따라서 함수 호출에 걸리는 시간이 매번 delay 밀리초보다 길면, 모든 함수가 쉼 없이 계속 연속 호출된다. 
    (함수 호출에 걸리는 시간이 200ms라고 하면 delay 100ms 보다 길기 때문에 계속 연속 호출)

100밀리초마다 func(i++)가 실행된다. 

 

let i = 1;
setInterval(function () {
  func(i++);
}, 100);

◾️ 중첩 setTimeout을 이용한 예시

  • 중첩 setTimeout을 사용하면 명시한 지연(100ms)이 보장된다. 
  • 이전 함수의 실행이 종료된 이후에 다음 함수 호출에 대한 계획이 세워지기 때문

let i = 1;
setInterval(function run() {
  func(i++);
  setTimeout(run, 100);
}, 100);

 

⚠️ 가비지 컬렉션과 setInterval, setTimeout

◾️ setInterval이나 setTimeout에 함수를 넘기면, 함수에 대한 내부 참조가 새롭게 만들어지고 이 참조 정보는 스케줄러에 저장된다. 따라서 해당 함수를 참조하는 것이 없어도 setInterval과 setTimeout에 넘긴 함수는 가비지 컬렉션의 대상이 되지 않는다. 
◾️ 스케줄러가 함수를 호출할 때까지 함수는 메모리에 유지된다. 
◾️ setInterval의 경우는, clearInterval이 호출되기 전까지 함수에 대한 참조가 메모리에 유지된다. 

외부 렉시컬 환경을 참조하는 함수가 있다고 할 때 이 함수가 메모리에 남아있는 동안엔 외부 변수 역시 메모리에 남아있다. 그런데 이렇게 되면 실제 함수가 차지했어야 하는 공간보다 더 많은 메모리 공간이 사용된다. 이런 부작용을 방지하고 싶다면 스케줄링할 필요가 없어진 함수는 아무리 작더라도 취소하는 것이 좋다. 

 

 

대기 시간이 0인 setTimeout

◾️ setTimeout(func, 0)이나 setTimeout(func)를 사용하면 setTimeout의 대기 시간을 9으로 설정할 수 있다.

◾️ 대기시간을 0으로 설정하면 func을 가능한 한 빨리 실행할 수 있다. 다만, 이때 스케줄러는 현재 실행중인 스크립트의 처리가 종료된 이후에 스케줄링한 함수를 실행한다. 

◾️ 현재 스크립트의 실행이 종료된 직후에 원하는 함수가 실행될 수 있게 할 수 있다. 

setTimeout(() => alert("World"));

alert("Hello");

◾️ setTimeout는 0밀리초 후에 함수 호출하기라는 할일을 계획표에 기록해주는 역할

◾️ 스케줄러는 현재 스크립트(alert)의 실행이 종료되고 나서야 계획표에 어떤 할일이 적혀있는지 확인

◾️ alert ➡️ setTimeout이 실행

 

 

ℹ️ 브라우저 환경에서 실제 대기 시간은 0이 아니다. 

◾️ 브라우저는 HTML5 표준에서 정한 중첩 타이머 실행 간격 관련 제약을 준수한다. 

◾️ 해당 표준엔 다섯 번째 중첩 타이머 이후엔 대기 시간을 최소 4밀리초 이상으로 강제해야한다 라는 제약이 명시되어 있다. 

 

◾️ 예시 내 setTimeout은 지연 없이 run을 다시 호출할 수 있게 스케줄링 되어 있다. 

◾️ 배열 times에는 실제 지연 간격에 대한 정보가 기록되도록 해놓았다. 

let start = Date.now();
let times = [];

setTimeout(function run() {
  times.push(Date.now() - start); // 이전 호출이 끝난 시점과 현재 호출이 시작된 시점의 시차를 기록
  
  if(start + 100 < Date.now()) console.log(time); // 지연 간격이 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

◾️ 앞쪽 타이머들은 스펙에 적힌 것처럼 지연 없이 바로 실행된다. 

◾️ 다섯 번째 중첩 타이머 이후엔 지연 간격이 4밀리초 이상이 되어 9, 15, 20, 24 ... 와 같은 값이 저장된다. 

 

◾️ 이런 제약은 setTimeout뿐만 아니라 setInterval에도 적용된다. setInterval(f)도 처음 몇 번은 함수 f를 지연 없이 실행하지만, 나중엔 지연 간격을 4밀리초 이상으로 늘려버린다. 

◾️ 서버측엔 이런 제약이 없다. Node.js의 process.nextTick과 setImmediate를 이용하면 비동기 작업을 지연 없이 실행할 수 있다

 

 

📝 요약

◽️ setInterval(func, delay, ...args)과 setTimeout(func, delay, ...args)은 delay밀리초 후에 func을 규칙적으로, 또는 한번 실행하도록 해준다. 
◽️ setInterval˙setTimeout을 호출하고 반환받은 값을 clearInterval˙clearTimeout에 넘겨주면 스케줄링을 취소할 수 있다. 
◽️ 중첩 setTimeout을 사용하면 setInterval을 사용한 것보다 유연하게 코드를 작성할 수 있다. 지연 간격 보장의 장점
◽️ 대기 시간이 0인 setTimeout(setTimeout(func, 0)) or setTimeout(func))을 사용하면 현재 스크립트의 실행이 완료된 후 가능한 한 빠르게 원하는 함수를 호출 할 수 있다. 
◽️ 지연 없이 중첩 setTimeout을 5회 이상 호출하거나 지연 없는 setInterval에서 호출이 5회 이상 이뤄지면, 4밀리초 이상의 지연 간격이 강제로 더해진다. 이는 브라우저에서만 적용되는 사항이며, 하위 호환성을 위해 유지되고 있다. 

◽️ 스케줄링 메서드를 사용할 땐 명시한 지연 간격이 보장되지 않을 수도 있다는 점에 유의해야 한다. 

◽️ 다음과 같은 상황에서 브라우저 내 타이머가 느려지면 지연 간격이 보장되지 않는다. 
    ◾️ CPU가 과부하 상태인 경우
    ◾️ 브라우저 탭이 백그라운드 모드인 경우
    ◾️ 노트북이 배터리에 의존해서 구동 중인 경우
◽️ 이런 상황에서 타이머의 최소 지연 시간은 300밀리초에서 심하면 1,000밀리초까지 늘어난다. 연장 시간은 브라우저나 구동 중인 운영 체제의 성능 설정에 따라 다르다. 


 



📝 오늘 새롭게 배운 내용 📝

 

✅ 어떤 시간 후에 어떤 일을 하고 싶다면 setTimeout

✅ 일정 시간 마다 어떤 일을 하고 싶다면 setInterval

✅ 타이머 함수는 타이머 식별자를 반환한다. 주로 timerId 변수에 값을 할당해서 사용

타이머 식별자는 숫자이다. 똑같은 기능의 타이머를 다시 설정하면 타이머 식별자의 숫자는 바뀐다!!!

✅ 일정 간격을 두고 실행 할땐 setInterval 타이머 대신 중첩 setTimeout을 사용는 방법이 있다. 

setTimeout안에 setTimeout ... 재귀 타이머??

호출 결과에 따라 다음 호출을 원하는 방식으로 조정해 스케줄링 할 수 있기 때문에 유연한 방법이다. 

setInterval의 delay 시간에는 함수를 실행하는데 소모되는 시간도 포함되어 있다. 

 중첩 setTimeout는 이전 함수의 실행이 종료된 이후에 다음 함수에 대한 계획이 세워지기 때문에 지연 시간이 보장된다. 

 대기 시간이 0으로 설정하면 가능한 한 빨리 함수를 실행시킬 수 있지만, 4개 이상 중첩되있는 경우에는 지연시간이 증가된다. 

브라우저 환경에서만 적용된다. 

✅ 타이머까지 날 어렵게 한다 ㅠ ㅠ ㅠ 

 

 

📌 [JAVASCRIPT_INFO setTimeout과 setInterval을 이용한 호출 스케줄링]

 

setTimeout과 setInterval을 이용한 호출 스케줄링

 

ko.javascript.info

 

 

 

'모던 자바스크립트' 카테고리의 다른 글

6.10 함수 바인딩  (0) 2020.09.01
6.9 call/apply와 데코레이터, 포워딩  (0) 2020.08.31
6.7 new Function 문법  (0) 2020.08.25
6.6 객체로서의 함수와 기명 함수 표현식  (0) 2020.08.24
6.5 전역 객체  (0) 2020.08.22