Quick Flow
async function loadStatus(shutdownSignal) {
const signal = AbortSignal.any([
AbortSignal.timeout(3000),
shutdownSignal,
]);
try {
return await fetch("https://api.example.com/status", { signal });
} catch (error) {
if (signal.aborted) {
console.warn("request canceled:", signal.reason);
return null;
}
throw error;
}
}
const shutdown = new AbortController();
process.once("SIGTERM", () => shutdown.abort(new Error("server shutdown")));
await loadStatus(shutdown.signal);단일 제한 시간에는 AbortSignal.timeout(ms)를 넘깁니다. 사용자 취소나 종료처럼 외부에서 끊어야 하면 AbortController를 만들고, 둘을 함께 받아야 하면 AbortSignal.any()로 하나의 signal을 만듭니다. 취소 여부는 에러 이름보다 전달한 signal.aborted와 signal.reason으로 판별합니다.
타임아웃 구조
먼저 머릿속에 넣어둘 기본형은 아래입니다.
- 직접 취소 제어:
new AbortController() - 단일 타임아웃:
AbortSignal.timeout(ms) - 여러 조건 결합:
AbortSignal.any([...]) - 취소 여부와 이유:
signal.aborted,signal.reason - 직접 만든 타이머 정리:
clearTimeout(...)
단일 타임아웃
AbortSignal.timeout()
단일 요청 타임아웃은 이쪽이 제일 간단합니다.
await fetch(url, { signal: AbortSignal.timeout(2000) });별도 취소 주체가 없는 단순 timeout이라면 이 패턴이 기준입니다. 이 신호는 시간이 지나면 스스로 abort되므로 직접 clearTimeout()할 대상도 없습니다.
신호 결합
AbortSignal.any()
실전에서는 보통 취소 조건이 하나가 아닙니다.
- 사용자 취소
- 서버 종료
- 타임아웃
이럴 때 여러 signal을 합쳐 하나의 취소 조건으로 다루는 것이 깔끔합니다.
const signal = AbortSignal.any([
AbortSignal.timeout(3000),
controller.signal,
]);취소 이유를 같은 에러로 뭉개지 않는다
timeout, 사용자 취소, 상위 shutdown은 모두 abort로 연결할 수 있지만, 운영에서는 왜 취소됐는지 구분할 필요가 많습니다. 그래서 abort 자체를 정상 흐름으로 먼저 분리하고, signal.reason까지 같이 기록하는 편이 좋습니다. AbortSignal.timeout()으로 끝난 경우에는 timeout 이유가, 직접 controller.abort(reason)을 호출한 경우에는 그 reason이 남습니다.
abort를 건 쪽이 정리 책임도 가진다
직접 setTimeout으로 controller를 abort했다면, 성공/실패/취소 어느 경로든 타이머 정리를 같이 해야 합니다. 또한 signal을 인자로 받는 API만 실제 취소를 수행합니다. 지원하지 않는 Promise에 signal을 붙여도 작업이 멈추지는 않습니다.
언제 어떤 신호를 쓸까
체크포인트
- 네트워크 요청 timeout
- stream/pipeline cancel
- 장시간 작업의 외부 중단
- UI 취소 버튼과 timeout을 동시에 연결할 때
- 단일 timeout이면
AbortSignal.timeout - 직접 제어면
AbortController - 여러 조건 결합이면
AbortSignal.any - 취소와 실패는 분리 처리
- abort를 건 쪽이 타이머와 리소스 정리도 같이 담당
주의할 점
취소를 일반 에러처럼 전부 로깅하면 실제 장애와 정상 취소가 섞여서 운영 신호가 흐려집니다. Abort 계열 에러는 먼저 걸러내고, 그다음 진짜 실패만 로깅하는 편이 맞습니다.
참고 링크
2 sources