← JavaScript 강의 목록으로
⏱️
비동기
중급 · 선수: 13_고차함수

14. 콜백과 타이머

동기 코드는 한 줄씩 차례로 실행되며 한 작업이 끝나야 다음으로 넘어갑니다. 비동기 코드는 "나중에 실행될 일"을 예약하고 즉시 다음 코드로 넘어갑니다.

setTimeout콜백비동기이벤트 루프
소요 시간
약 1시간
난이도
📊 중급
선수 조건
🎯 단원 13
결과물
동기 코드는 한 줄씩 차례로 실행되며 한 작업이 끝나야 다음으로 넘어갑니다. 비동기 코드는 "나중에 실행될 일"을 예약하고 즉시 다음 코드로 넘어갑니다.

이 강의에서 배우는 것

  • 1동기와 비동기의 차이를 이해한다.
  • 2`setTimeout`, `setInterval`, `clearTimeout`, `clearInterval` 을 사용한다.
  • 3이벤트 루프의 개념을 큰 그림으로 이해한다.
  • 4콜백 지옥이 왜 문제인지 인지하고 다음 단원(Promise)의 필요성을 느낀다.
  • 5타이머 ID를 관리하여 리소스 누수를 방지한다.

소개

동기 코드는 한 줄씩 차례로 실행되며 한 작업이 끝나야 다음으로 넘어갑니다. 비동기 코드는 "나중에 실행될 일"을 예약하고 즉시 다음 코드로 넘어갑니다.

핵심 개념

1. 동기 vs 비동기

동기 코드는 한 줄씩 차례로 실행되며 한 작업이 끝나야 다음으로 넘어갑니다. 비동기 코드는 "나중에 실행될 일"을 예약하고 즉시 다음 코드로 넘어갑니다.

javascript
console.log("A");
setTimeout(() => console.log("B"), 0);
console.log("C");
// 출력: A, C, B

`setTimeout(fn, 0)` 조차 동기 코드가 모두 끝난 뒤에 실행됩니다.

2. setTimeout 과 clearTimeout

`setTimeout(fn, ms)` 은 지정한 밀리초 후에 콜백을 한 번 실행합니다. 반환값은 타이머 ID이며, `clearTimeout(id)` 로 취소할 수 있습니다.

javascript
const id = setTimeout(() => console.log("실행되지 않음"), 1000);
clearTimeout(id);

`ms` 는 "최소 대기 시간"이지 "정확한 시간"이 아닙니다. 메인 스레드가 바쁘면 더 늦게 실행됩니다.

3. setInterval 과 clearInterval

`setInterval(fn, ms)` 은 일정 간격으로 콜백을 반복합니다. 반드시 더 이상 필요 없을 때 `clearInterval` 로 정리해야 합니다.

javascript
let n = 0;
const id = setInterval(() => {
  n += 1;
  if (n >= 3) clearInterval(id);
  console.log(n);
}, 500);

4. 이벤트 루프 (큰 그림)

자바스크립트는 싱글 스레드이지만, 호스트 환경(브라우저/Node)이 제공하는 타이머/네트워크 API는 별도로 동작합니다. 콜백은 큐에 쌓였다가 호출 스택이 비었을 때 하나씩 실행됩니다. 자세한 내용은 16단원 이후에서 더 다룹니다.

5. 콜백 지옥

비동기 작업이 연달아 필요하면 콜백 안에 콜백을 또 넣게 되어 들여쓰기가 깊어집니다.

javascript
step1((a) => {
  step2(a, (b) => {
    step3(b, (c) => {
      console.log(c);
    });
  });
});

이 문제를 풀기 위해 15단원에서 `Promise` 를, 16단원에서 `async/await` 을 배웁니다.

핵심 예제

파일다루는 내용
01_settimeout.jssetTimeout/clearTimeout 기본
02_setinterval.jssetInterval 로 반복 + 종료 조건
03_event_loop.js동기/비동기 실행 순서 관찰
04_callback_hell.js콜백 중첩의 문제 시연

src/01_settimeout.js

javascript
/**
 * setTimeout 기본 사용법.
 * 1초 뒤 메시지를 출력하고, 또 하나의 타이머는 즉시 취소합니다.
 */
console.log("시작");

setTimeout(() => {
  console.log("1초 뒤 실행됨");
}, 1000);

const cancelId = setTimeout(() => {
  console.log("이 메시지는 출력되지 않습니다.");
}, 500);
clearTimeout(cancelId);

console.log("끝 (동기 코드)");

src/02_setinterval.js

javascript
/**
 * setInterval 로 0.5초마다 카운트하고 3회 뒤 멈춥니다.
 */
let count = 0;
const intervalId = setInterval(() => {
  count += 1;
  console.log(`tick ${count}`);
  if (count >= 3) {
    clearInterval(intervalId);
    console.log("정리 완료");
  }
}, 500);

src/03_event_loop.js

javascript
/**
 * 동기 코드와 비동기 콜백의 실행 순서를 관찰합니다.
 * 예상 출력: A -> D -> C -> B
 */
console.log("A");

setTimeout(() => console.log("B"), 100);
setTimeout(() => console.log("C"), 0);

console.log("D");

src/04_callback_hell.js

javascript
/**
 * 콜백이 중첩되는 패턴(콜백 지옥)을 보여줍니다.
 * 다음 단원에서 Promise 로 어떻게 평탄해지는지 비교해 보세요.
 */
function step(name, ms, callback) {
  setTimeout(() => {
    console.log(`${name} 완료`);
    callback();
  }, ms);
}

step("1단계", 200, () => {
  step("2단계", 200, () => {
    step("3단계", 200, () => {
      console.log("모든 단계 완료");
    });
  });
});

자주 하는 실수

  1. `setTimeout(fn(), 1000)` 처럼 함수를 즉시 호출(괄호)해 결과값을 전달.
  2. `setInterval` 을 만들고 `clearInterval` 을 잊어버려 누수 발생.
  3. `setTimeout(fn, 0)` 이 즉시 실행될 거라고 기대.
  4. 비동기 콜백 안에서 throw 한 에러를 바깥 try/catch 로 잡으려고 시도.
  5. 콜백 내부에서 `this` 가 외부와 다른 것을 모르고 사용 (화살표 함수로 해결).

FAQ

Q1. setTimeout 0ms 와 즉시 실행의 차이는?

0ms 도 한 번 큐를 거치므로 현재 동기 코드가 모두 끝난 뒤 실행됩니다.

Q2. setInterval 과 재귀 setTimeout 차이?

setInterval 은 콜백 실행 시간과 상관없이 간격을 유지하려 하고, 재귀 setTimeout 은 콜백 종료 후 다음 타이머를 설정합니다.

Q3. 브라우저와 Node 의 타이머가 같은가요?

API 시그니처는 같지만 최소 지연 시간, 우선순위 큐 등 세부 사항이 다릅니다.

과제

  1. 1초마다 카운트다운하는 함수를 작성하세요.
  2. 콜백 3개를 순차 실행하는 코드를 작성하세요.

homework/README.md

## 1. 카운트다운 `countdown(n)` 함수를 작성하세요. n 부터 0까지 1초 간격으로 출력하고, 0 출력 후 "발사!" 를 출력합니다.

## 2. 순차 실행 세 개의 비동기 작업을 콜백으로 차례대로 실행하는 코드를 작성하세요. 각 작업은 `setTimeout` 으로 시뮬레이션합니다.

답안은 `answer/` 디렉터리를 참고하세요.

homework/answer/homework_01.js

javascript
/**
 * n 부터 0까지 1초 간격으로 출력 후 "발사!" 를 출력합니다.
 * @param {number} n 시작 숫자
 */
function countdown(n) {
  let current = n;
  const id = setInterval(() => {
    if (current < 0) {
      clearInterval(id);
      console.log("발사!");
      return;
    }
    console.log(current);
    current -= 1;
  }, 1000);
}

countdown(3);

homework/answer/homework_02.js

javascript
/**
 * 세 개의 비동기 작업을 순차적으로 실행합니다.
 * @param {string} name 작업 이름
 * @param {Function} done 완료 콜백
 */
function task(name, done) {
  setTimeout(() => {
    console.log(`${name} 완료`);
    done();
  }, 300);
}

task("A", () => {
  task("B", () => {
    task("C", () => {
      console.log("모든 작업 종료");
    });
  });
});

다음 단원

[15_Promise](../15_Promise/) — 콜백 지옥을 해결하는 Promise 패턴

예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗