본문으로 건너뛰기

나중에 하기: setTimeout

자바스크립트 목차비동기레슨 58 / 76

목차

지금까지 본 코드는 모두 1번 줄부터 마지막 줄까지 적힌 순서대로 한 줄씩 끝내며 내려왔습니다. 하지만 카페에서 커피를 내리는 데 3초가 걸린다고 그 3초 동안 프로그램이 멈춰 서 있으면, 그동안 다음 손님의 주문을 받지도 못하고 화면 단추도 먹통이 됩니다. 자바스크립트는 오래 걸리는 일을 할 일 줄에 맡겨 두고 다음 줄로 바로 넘어간 뒤, 지금 도는 코드가 끝까지 다 끝나면 그때 맡겨 둔 함수를 꺼내 실행합니다(규칙 8). 이 단원을 이미 안다면 11단원 연습 문제의 다섯 문제를 먼저 풀어 보세요.

파이썬을 배웠다면 — 이 단원에서 다른 곳
  • 기다리는 방식: 파이썬의 time.sleep(1)은 그 줄에서 프로그램 전체를 1초 동안 멈춰 세운 뒤 다음 줄로 내려갑니다. 반면 자바스크립트의 setTimeout( 함수, 1000 )⁠은 함수를 예약만 해 두고 기다리지 않은 채 바로 다음 줄을 실행합니다.
  • 규칙 8 (할 일 줄과 Promise 줄): 예약해 둔 함수는 시간이 되었다고 해서 지금 돌고 있는 코드 중간에 끼어들지 못합니다. 반드시 지금 실행 중인 코드가 파일 끝까지 다 돈 뒤에야 대기 줄에서 꺼내 실행합니다.

setTimeout으로 나중에 실행하기

setTimeout(함수, 밀리초)는 첫째 인자로 넘긴 콜백 함수를 둘째 인자로 적은 시간(1000밀리초 = 1초) 뒤에 실행하도록 예약합니다.

console.log('주문 받기');
setTimeout(() => {
  console.log('커피 완성');
}, 100);
console.log('영수증 출력');
실행 결과
주문 받기
영수증 출력
커피 완성

2~4번 줄의 setTimeout은 100밀리초 동안 그 자리에 멈춰 기다리지 않습니다. 화살표 함수를 맡겨 두고 곧바로 5번 줄로 내려가 '영수증 출력'을 먼저 찍은 뒤, 100밀리초가 지나면 '커피 완성'을 찍습니다.

0밀리초로 걸어도 나중에 돈다 (규칙 8)

만약 기다리는 시간을 0밀리초로 적으면 그 자리에서 바로 실행될까요? 아래 코드와 모션 그림을 따라가며 1, 2, 3 가운데 무엇이 먼저 찍히는지 확인해 보세요.

console.log(1);
setTimeout(() => {
  console.log(2);
}, 0);
console.log(3);
실행 결과
1
3
2

5단계 중 1단계. 1번 줄의 `console.log("1")`이 지금 도는 칸(호출 스택)에서 바로 실행되어 실행 결과 칸에 `1`을 찍습니다.

  • 지금 도는 칸: 현재 위에서 아래로 실행 중인 코드입니다.
  • 할 일 줄: setTimeout이 맡긴 함수가 차례를 기다리는 줄입니다.
  • 규칙 8: setTimeout(..., 0)은 기다리는 시간이 0밀리초라도 함수를 그 자리에서 부르지 않고 할 일 줄 끝에 세웁니다. 할 일 줄에 선 함수는 지금 도는 코드가 마지막 줄까지 모두 끝난 뒤에야 꺼내져 실행되므로 1 → 3 → 2 차례로 찍힙니다.

타이머 여러 개와 기다리는 시간

타이머를 두 개 걸면, 적은 시간이 짧은 타이머가 먼저 기다림을 마치고 할 일 줄에 섭니다.

setTimeout(() => {
  console.log('커피 200ms');
}, 200);
setTimeout(() => {
  console.log('쿠키 50ms');
}, 50);
console.log('주문 끝');
실행 결과
주문 끝
쿠키 50ms
커피 200ms

지금 도는 코드의 '주문 끝'이 가장 먼저 찍히고, 50밀리초 타이머가 먼저 할 일 줄에 들어와 '쿠키 50ms'를 찍은 뒤, 200밀리초 타이머가 '커피 200ms'를 찍습니다. (일정한 간격마다 함수를 되풀이해 부를 때는 setInterval을 씁니다.)

자주 틀리는 곳

1. 넘긴 콜백 함수는 늘 나중에 돈다고 생각함

8단원에서 본 배열의 map이나 filter에 넘긴 콜백 함수도 나중에 돌까요? 그렇지 않습니다. 콜백 함수를 지금 부를지 나중에 부를지는 그 함수를 받은 쪽이 정합니다.

const arr = [1, 2].map((n) => {
  console.log('map:', n);
  return n * 10;
});
console.log('끝:', arr);
실행 결과
map: 1
map: 2
끝: [ 10, 20 ]

map은 넘겨받은 함수를 그 자리에서 지금 바로 부르므로 map: 1, map: 2가 '끝'보다 먼저 찍힙니다. 반면 setTimeout은 넘겨받은 함수를 할 일 줄에 넣어 나중에 부릅니다.

2. 함수 대신 괄호를 붙여 호출한 결과를 넘김

setTimeout의 첫째 자리에는 함수 자체를 넘겨야 합니다. say()처럼 괄호를 붙여 적으면 그 자리에서 say()를 바로 실행해 버리고, 돌려준 undefined가 setTimeout에 넘어가 Node에서 TypeError가 납니다.

function say() {
  console.log('알림');
}
setTimeout(say, 10);
실행 결과
알림

인자를 넘겨야 할 때는 () => say('라테')처럼 화살표 함수로 감싸서 setTimeout에 넘깁니다.

3. 반복문 안의 setTimeout과 let 칸 기억하기

for (let i = 0; ...) 반복문 안에서 setTimeout을 걸면, 9단원 클로저: 만들어진 칸을 기억하는 함수에서 배운 대로 let i는 바퀴마다 새 블록 칸을 만들고 각 화살표 함수가 자기 바퀴의 i 칸을 기억합니다.

for (let i = 0; i < 3; i += 1) {
  setTimeout(() => {
    console.log('번호:', i);
  }, 10);
}
실행 결과
번호: 0
번호: 1
번호: 2

요약

  • setTimeout(함수, 밀리초): 기다리는 동안 프로그램을 멈추지 않고 함수를 예약해 둔 채 바로 다음 줄로 넘어간다.
  • 규칙 8: setTimeout에 맡긴 함수는 0밀리초라도 지금 도는 코드가 끝까지 다 돈 뒤에 할 일 줄에서 꺼내 실행된다.
  • 타이머 여러 개를 걸면 대기 시간이 먼저 끝난 함수부터 할 일 줄에 선다.
  • map · filter의 콜백은 지금 바로 돌고, setTimeout의 콜백은 나중에 돈다.

연습 문제

문제 1

다음 코드를 실행했을 때 출력되는 줄을 마지막 줄까지 순서대로 쓰세요.

console.log('시작');
setTimeout(() => {
  console.log('A');
}, 100);
setTimeout(() => {
  console.log('B');
}, 0);
console.log('끝');
힌트

지금 도는 코드('시작', '끝')가 먼저 다 실행된 뒤, 대기 시간이 짧은 0밀리초 타이머('B')가 100밀리초 타이머('A')보다 먼저 실행됩니다.

정답
실행 결과
시작
끝
B
A
  • 지금 도는 코드인 1번 줄 '시작'과 8번 줄 '끝'이 먼저 찍힙니다.
  • 그다음 0밀리초 타이머의 'B'가 먼저 찍히고, 마지막으로 100밀리초 타이머의 'A'가 찍힙니다.

문제 2

다음 코드는 10밀리초 뒤에 '벨 울림'이 찍히고 그보다 먼저 '대기 중'이 찍혀야 하는데, bell()에 괄호를 붙여 넘겨서 실행하자마자 '벨 울림'이 찍히고 TypeError로 멈췄습니다. 4번 줄 한 줄만 고쳐 바르게 동작하게 만드세요.

function bell() {
  console.log('벨 울림');
}
// setTimeout(bell(), 10);
힌트

bell()의 괄호 ()를 빼고 함수 이름표 bell만 넘기세요.

정답
  • 고친 줄: setTimeout(bell, 10);
function bell() {
  console.log('벨 울림');
}
setTimeout(bell, 10);
console.log('대기 중');
실행 결과
대기 중
벨 울림
  • 괄호 없이 bell을 넘겨야 할 일 줄에 함수가 등록되어 '대기 중' 뒤에 '벨 울림'이 찍힙니다.

문제 3

let msg = '준비';를 만든 뒤 setTimeout으로 10밀리초 뒤에 msg를 찍게 예약하고, 바로 아랫줄에서 msg = '출발';로 바꿉니다. 무엇이 찍히는지 확인하는 코드를 완성하세요.

출발
let msg = '준비';
// 10ms 뒤 msg 찍기 예약
// msg = '출발' 대입
힌트

화살표 함수는 msg의 옛 값 '준비'를 복사해 두지 않고 msg 이름표를 기억합니다(규칙 5). 10밀리초 뒤 함수가 실행될 때는 이미 msg가 '출발'로 바뀌어 있습니다.

정답
let msg = '준비';
setTimeout(() => {
  console.log(msg);
}, 10);
msg = '출발';
실행 결과
출발

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

자바스크립트 목차로