본문 바로가기
TYLER SONGBlog
블로그 목록
Frontend

delay, timeout: 시간을 기준으로 값의 흐름을 제어하는 연산자

delay는 옵저버블(Observable)이 방출하는 값을 지정한 시간만큼 미뤄서 내보내는 연산자이고, timeout은 정해진 시간 안에 값이 도착하지 않으면 에러를 발생시키는 연산자다. 둘 다 시간을 축으로 스트림의 동작을 조절한다는 공통점이 있지만, delay는 정상 흐름을 늦추는 데 쓰이고 timeout은 응답 지연을 감지해서 예외 처리하는 데 쓰인다

송민성6분 읽기

1. 개념

delay는 소스 옵저버블(source Observable)에서 방출되는 각 값을 지정한 시간(밀리초)만큼 지연시킨 뒤 구독자(subscriber)에게 전달하는 연산자다. 값의 순서나 개수는 바뀌지 않고, 오직 방출 시점만 뒤로 밀린다.

timeout은 소스 옵저버블이 지정한 시간 안에 값을 방출하지 않으면 TimeoutError를 발생시키는 연산자다. 값이 제때 도착하면 아무 영향을 주지 않고 그대로 통과시키지만, 시간 내에 도착하지 않으면 스트림을 에러로 종료시킨다.

두 연산자는 모두 시간이라는 축을 다루지만 목적이 반대다. delay는 "느리게 만드는" 연산자이고, timeout은 "너무 느리면 잘라내는" 연산자다.

2. 왜 사용하는가

실무에서 API 응답이나 사용자 입력 같은 비동기 이벤트를 다룰 때, 값이 언제 도착하느냐가 로직에 영향을 준다. delay는 UI에서 로딩 애니메이션을 최소 시간 동안 보여주거나, 테스트 환경에서 네트워크 지연을 흉내 낼 때 유용하다. timeout은 서버 응답이 너무 오래 걸릴 때 무한정 기다리지 않고 에러 처리 로직으로 넘어가게 만들어, 애플리케이션이 멈춘 것처럼 보이는 상황을 방지한다.

3. 동작 원리

delay는 내부적으로 소스에서 값이 방출될 때마다 타이머(setTimeout과 유사한 스케줄러 기반 동작)를 설정하고, 지정된 시간이 지난 뒤에 그 값을 구독자에게 전달한다. 완료(complete) 신호도 마지막 값과 함께 지연되어 전달된다. 에러(error)는 지연 없이 즉시 전달되는 점이 특징이다.

timeout은 소스를 구독하는 순간부터 내부 타이머를 시작한다. 지정한 시간 안에 다음 값이 도착하면 타이머를 리셋하고 다시 기다리기 시작한다. 만약 타이머가 만료되기 전에 값이 오지 않으면 TimeoutError를 던지면서 스트림을 종료시킨다. 즉 timeout은 "전체 구독 시간"이 아니라 "각 값 사이의 간격"을 기준으로 동작한다는 점을 이해해야 한다.

4. 예제

먼저 delay의 기본 사용법을 살펴보면, of(1, 2, 3).pipe(delay(1000)).subscribe(value => console.log(value)) 같은 코드를 작성할 수 있다. 이 코드는 1, 2, 3이라는 값을 즉시 방출하는 소스에 delay(1000)을 적용해서, 1초 후에 세 값이 한꺼번에 순서대로 출력되게 만든다. of는 동기적으로 값을 방출하기 때문에 값들 사이의 간격 없이 지연 시간만큼 미뤄진 뒤 연달아 나온다.

그다음으로 timeout의 기본 사용법을 보면, interval(500).pipe(timeout(300)).subscribe({ next: value => console.log(value), error: err => console.log('타임아웃 발생:', err.message) }) 같은 코드를 작성할 수 있다. interval(500)은 500밀리초마다 값을 방출하는데, timeout(300)은 300밀리초 안에 값이 와야 한다고 설정했으므로 첫 값이 도착하기 전에 타임아웃이 발생해서 에러 콜백이 실행된다.

실무에서 자주 쓰이는 패턴으로, HTTP 요청을 모사한 옵저버블에 timeoutcatchError를 함께 쓰는 경우가 있다. 먼저 fetchDataObservable.pipe(timeout(5000), catchError(err => of('기본값 반환'))) 형태로 작성하고, 그다음 .subscribe(result => console.log(result))로 구독하면, 5초 안에 응답이 오면 정상 값이 출력되고 그렇지 않으면 기본값이 대신 출력된다.

5. 실무 사용 사례

delay는 스켈레톤 UI나 로딩 스피너가 너무 빨리 사라져서 깜빡이는 현상을 방지하기 위해, 실제 데이터가 즉시 도착해도 최소 300밀리초 정도는 로딩 상태를 유지하도록 강제하는 데 쓰인다. 또한 테스트 코드에서 실제 네트워크 지연을 재현해서 로딩 처리 로직이 올바르게 동작하는지 검증할 때도 사용한다.

timeout은 외부 API 호출이 응답 없이 멈춰버리는 상황을 방지하기 위해, 일정 시간 안에 응답이 없으면 재시도(retry)하거나 사용자에게 오류 메시지를 보여주는 흐름을 구현할 때 핵심적으로 쓰인다. WebSocket 연결에서 하트비트(heartbeat) 신호가 일정 시간 안에 오지 않으면 연결이 끊긴 것으로 간주하고 재연결을 시도하는 로직에도 자주 활용된다.

6. 주의할 점

delay는 값의 순서를 바꾸지 않지만, 만약 소스가 이미 여러 값을 빠르게 연달아 방출한 상태라면 지연된 값들이 한꺼번에 몰려서 나올 수 있다는 점을 인지해야 한다. 각 값이 개별적으로 지연되기 때문에 원래 방출 간격이 유지된다고 착각하면 안 된다.

timeout은 "전체 스트림이 몇 초 안에 완료되어야 한다"는 의미가 아니라 "각 값 사이의 간격이 몇 초를 넘으면 안 된다"는 의미로 동작한다는 점을 혼동하지 말아야 한다. 예를 들어 값이 1초마다 계속 나오는 스트림에 timeout(500)을 걸면, 전체적으로는 스트림이 오래 지속되더라도 각 값 사이 간격이 500밀리초를 넘기는 순간 즉시 에러가 발생한다.

또한 timeout이 발생시키는 에러를 처리하지 않으면 구독이 예외로 종료되므로, 실무에서는 반드시 catchErrorretry 같은 에러 처리 연산자와 함께 사용해야 한다.

7. 핵심 정리

delay는 값의 방출 시점을 뒤로 미루는 연산자이고, timeout은 정해진 시간 안에 값이 오지 않으면 에러를 발생시키는 연산자다. 두 연산자 모두 시간을 기준으로 스트림의 흐름을 제어하지만, 하나는 흐름을 늦추는 목적이고 다른 하나는 지연을 감지해서 예외로 처리하는 목적이라는 차이를 명확히 구분해야 한다. 실무에서는 UI 경험 개선이나 네트워크 응답 지연 감지 같은 상황에서 각각의 특성에 맞게 활용된다.

© 2026 Tyler Song