TypeScript에는 내장 sleep이 없습니다. 지연 시간이 지나면 setTimeout이 이행시키는 Promise<void>를 반환하는 함수를 작성한 뒤, async 함수 안에서 await하세요.
await sleep(300)은 main만 멈춥니다. 프로그램이 얼어붙지 않으며, 기다리는 동안 타이머, 다른 async 함수, I/O는 계속 실행됩니다.
sleep 함수의 동작 원리
setTimeout(resolve, ms)는 ms 밀리초 뒤에 resolve가 호출되도록 예약하고, resolve를 호출하면 Promise가 처리됩니다. 그 Promise를 await하면 그때까지 async 함수가 일시 중단됩니다.
<void> 타입 인수가 중요합니다. 단독으로 쓴 new Promise(...)는 resolve 호출에서 타입을 추론하지 않으므로, 이것이 없으면 sleep은 Promise<unknown>을 반환합니다. 그리고 void여야 resolve를 값 없이 호출할 수 있습니다. Promise<number>에서 인수 없는 resolve()는 컴파일 오류입니다. 함수에 반환 타입을 선언했다면 타입 인수 대신 거기에 써도 됩니다.
setTimeout은 정확한 시간이 아니라 최소 시간을 보장합니다. 콜백은 지연 시간이 지나고 이벤트 루프가 비었을 때 실행됩니다. 몇 밀리초 정도의 오차는 예상하세요.
루프 안에서 sleep하기
for나 for...of 루프 안의 await sleep()은 반복 사이에 멈추며, 속도 제한, 폴링, 터미널 애니메이션처럼 작업 속도를 조절할 때 이렇게 합니다.
forEach 안에서는 동작하지 않습니다. 각 async 콜백이 반환하는 Promise를 무시하므로 모든 반복이 한꺼번에 시작되고 아무것도 기다리지 않습니다. async/await 페이지에서 설명하듯 for나 for...of 루프를 쓰세요.
지연 후 재시도하기
실제로 흔한 용도입니다. 작업을 시도하고, 실패하면 기다렸다가 다시 시도하며, 매번 기다리는 시간을 두 배로 늘립니다(지수 백오프).
try 안의 return await fn()이 중요합니다. await가 없으면 거부된 Promise가 catch가 보기 전에 반환되어 버립니다.
취소할 수 있는 sleep
평범한 sleep은 중단할 수 없습니다. 사용자가 취소를 누르거나 종료가 시작되어 일찍 기다림을 멈추려면 AbortSignal을 넘기고, 신호가 오면 타이머를 지우세요.
긴 타이머가 지워졌으므로 프로그램은 abort 직후에 종료됩니다. clearTimeout이 없으면 Node는 5초가 다 될 때까지 계속 실행됩니다.
Node의 내장 Promise 타이머
Node는 node:timers/promises 모듈에 Promise 버전의 setTimeout을 제공하므로, Node 코드에서는 헬퍼를 직접 쓰는 대신 가져와 쓸 수 있습니다. 이행할 값과 AbortSignal도 받습니다.
import { setTimeout as sleep } from "node:timers/promises";
async function main() {
await sleep(1000); // wait one second
const result = await sleep(500, "done"); // resolves with "done"
const ac = new AbortController();
await sleep(10_000, undefined, { signal: ac.signal }); // cancellable
}
타입은 @types/node(npm i -D @types/node)에서 옵니다. 이 모듈은 브라우저에는 없으므로, 브라우저에서는 한 줄짜리 헬퍼가 어디서나 통하는 선택입니다.
블로킹 sleep은 없다
스레드가 있는 언어에는 현재 스레드를 막는 sleep이 있습니다. JavaScript는 이벤트 루프가 있는 단일 스레드에서 코드를 실행하므로, 블로킹 sleep은 다른 모든 것을 멈춰 버립니다. 타이머가 실행되지 않고, 네트워크 응답이 처리되지 않으며, 브라우저에서는 페이지가 응답하지 않습니다. 시계가 특정 시간을 지날 때까지 도는 바쁜 루프가 정확히 그런 일을 하며, 결코 올바른 도구가 아닙니다.
JavaScript와 TypeScript에서 기다리는 방법은 async sleep입니다. 함수가 일시 중단되고, 스레드는 자유로우며, 타이머가 실행되면 함수가 다시 이어집니다. Atomics.wait는 실제로 블로킹할 수 있지만 SharedArrayBuffer에서만 가능하고 브라우저는 메인 스레드에서 이를 금지합니다. 지연용이 아니라 워커 스레드 조정을 위해 존재합니다.
자주 묻는 질문
TypeScript에서 sleep은 어떻게 하나요?
const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));를 정의하고 async 함수 안에서 await sleep(1000)을 호출합니다. 프로그램의 나머지 부분을 막지 않고 그 함수만 약 1초 동안 멈춥니다.
TypeScript에 내장 sleep 함수가 있나요?
없습니다. TypeScript는 JavaScript에 타입을 더할 뿐 자체 런타임 라이브러리가 없고, JavaScript에는 sleep이 없습니다. Node에서는 가져와 쓸 수 있습니다: import { setTimeout as sleep } from "node:timers/promises". 브라우저에서는 한 줄짜리 헬퍼를 직접 작성하세요.
sleep에 왜 await가 필요한가요?
sleep(1000)은 Promise를 즉시 반환합니다. Promise가 이행될 때까지 감싸고 있는 async 함수를 멈추는 것은 await뿐입니다. await가 없으면 다음 줄이 곧바로 실행됩니다. await는 async 함수 안(또는 ES 모듈의 최상위)에서만 동작합니다.
TypeScript에서 동기 sleep으로 스레드를 막을 수 있나요?
일반 코드에서는 안 됩니다. JavaScript는 이벤트 루프가 있는 스레드 하나에서 실행되며, 바쁜 대기 루프는 타이머, 네트워크 콜백, 페이지 전체까지 모든 것을 멈춰 버립니다. 대신 async sleep을 쓰세요. Atomics.wait는 Node나 워커에서 스레드를 막을 수 있지만, 지연용이 아니라 저수준 워커 조정을 위한 것입니다.