[EXT-036][실무] 시간 제한과 사용자 취소 한 신호로 묶기
페이지 정보
작성자 기술팀장 작성일 26-09-09 02:37 조회 3 댓글 0본문
[이번 수업]
긴 작업은 사용자가 취소할 수도 있고, 정한 시간을 넘겨 자동으로 멈춰야 할 수도 있습니다. 이번에는 AbortSignal.timeout()과 AbortSignal.any()로 두 중단 조건을 한 신호로 묶고, 무엇이 먼저 멈췄는지 reason으로 구분합니다.
[선수지식]
Promise, async·await, 이벤트 리스너와 EXT-035의 AbortSignal 기본 사용법을 알면 좋습니다. 예제는 AbortSignal.any()를 제공하는 Node.js 20.3 이상에서 실행합니다.
[학습목표]
1. 사용자 취소와 시간 제한을 하나의 신호로 합칩니다.
2. AbortError와 TimeoutError를 구분합니다.
3. 중단 이유를 상위 코드로 전달합니다.
[핵심개념]
AbortController는 코드가 직접 중단을 알릴 때 사용합니다. controller.abort(reason)을 호출하면 signal이 중단 상태가 되고 reason을 보관합니다. 이유를 생략하면 AbortError인 DOMException이 사용됩니다.
AbortSignal.timeout(milliseconds)은 시간이 지나면 TimeoutError를 이유로 중단되는 신호를 만듭니다. AbortSignal.any([a, b])는 입력 중 하나라도 중단되면 함께 중단되는 새 신호를 만듭니다. 합친 신호에는 가장 먼저 중단시킨 입력의 reason이 남으며 나중에 바뀌지 않습니다. signal.throwIfAborted()는 중단되었다면 저장된 reason을 그대로 던집니다. 따라서 중단은 한 통로로 처리하고 원인은 구분할 수 있습니다.
[따라하기]
아래 내용을 cancel-race.mjs로 저장합니다.
```js
const userController = new AbortController();
const timeoutSignal = AbortSignal.timeout(80);
const combined = AbortSignal.any([
userController.signal,
timeoutSignal,
]);
setTimeout(() => {
userController.abort(
new DOMException('사용자 취소', 'AbortError'),
);
}, 20);
await new Promise((resolve) => {
combined.addEventListener('abort', resolve, { once: true });
});
console.log(`aborted=${combined.aborted}`);
console.log(`reason=${combined.reason.name}`);
try {
combined.throwIfAborted();
} catch (error) {
console.log(`caught=${error.name}`);
}
```
터미널에서 node cancel-race.mjs를 실행합니다. Windows PowerShell, macOS Terminal, Linux 셸 모두 같은 명령입니다. 예상 결과는 다음과 같습니다.
```text
aborted=true
reason=AbortError
caught=AbortError
```
20밀리초의 사용자 취소가 80밀리초 제한보다 먼저 도착했습니다. Node.js v25.8.1에서 이 결과를 확인했습니다.
[흔한 실수]
combined.aborted만 보면 재시도 여부를 잘못 판단할 수 있으므로 reason.name도 확인하세요. 이미 중단된 신호에는 나중에 등록한 abort 이벤트가 다시 발생하지 않습니다. 외부 신호를 받으면 먼저 aborted를 확인하거나 시작점에서 throwIfAborted()를 호출하세요. 리스너에는 { once: true }를 써서 불필요한 참조가 남지 않게 합니다. 작업 코드가 신호를 무시하면 실제 작업은 멈추지 않는다는 점도 주의합니다.
[보안 주의]
시간 제한은 권한 검사나 자원 제한을 대신하지 않습니다. 큰 입력이 자원을 오래 점유하지 못하도록 요청 크기와 동시 실행 수도 제한하세요. 중단 이유에 토큰, 개인정보, 전체 요청 본문을 넣거나 로그로 남기지 않습니다. 실습은 본인 소유의 로컬 환경에서만 실행합니다.
[직접 해볼 과제]
사용자 취소 타이머를 20에서 120밀리초로 바꿔 실행하세요. 80밀리초 제한이 먼저 도착해 reason과 caught가 TimeoutError가 되는지 확인합니다. 이어 두 시간을 모두 80밀리초로 맞춰 여러 번 실행하고, 같은 시각을 기대하는 설계가 불안정한 이유를 적어 보세요.
[확인문제]
1. AbortSignal.timeout()이 중단될 때 reason.name은 무엇인가요?
2. 두 입력 신호가 차례로 중단되면 합친 신호에는 어느 이유가 남나요?
3. abort 이벤트 리스너에 { once: true }를 권장하는 이유는 무엇인가요?
[다음 학습]
다음에는 합친 신호를 fetch에 전달하고 정상 응답, 사용자 취소, 시간 초과, 서버 오류를 구분해 처리합니다.
[공식 참고 자료]
- WHATWG DOM Standard — AbortSignal: https://dom.spec.whatwg.org/#interface-AbortSignal
- WHATWG DOM Standard — AbortController: https://dom.spec.whatwg.org/#interface-abortcontroller
- Node.js Global objects — AbortSignal.timeout(): https://nodejs.org/api/globals.html#static-method-abortsignaltimeoutdelay
- Node.js Global objects — AbortSignal.any(): https://nodejs.org/api/globals.html#static-method-abortsignalanysignals
긴 작업은 사용자가 취소할 수도 있고, 정한 시간을 넘겨 자동으로 멈춰야 할 수도 있습니다. 이번에는 AbortSignal.timeout()과 AbortSignal.any()로 두 중단 조건을 한 신호로 묶고, 무엇이 먼저 멈췄는지 reason으로 구분합니다.
[선수지식]
Promise, async·await, 이벤트 리스너와 EXT-035의 AbortSignal 기본 사용법을 알면 좋습니다. 예제는 AbortSignal.any()를 제공하는 Node.js 20.3 이상에서 실행합니다.
[학습목표]
1. 사용자 취소와 시간 제한을 하나의 신호로 합칩니다.
2. AbortError와 TimeoutError를 구분합니다.
3. 중단 이유를 상위 코드로 전달합니다.
[핵심개념]
AbortController는 코드가 직접 중단을 알릴 때 사용합니다. controller.abort(reason)을 호출하면 signal이 중단 상태가 되고 reason을 보관합니다. 이유를 생략하면 AbortError인 DOMException이 사용됩니다.
AbortSignal.timeout(milliseconds)은 시간이 지나면 TimeoutError를 이유로 중단되는 신호를 만듭니다. AbortSignal.any([a, b])는 입력 중 하나라도 중단되면 함께 중단되는 새 신호를 만듭니다. 합친 신호에는 가장 먼저 중단시킨 입력의 reason이 남으며 나중에 바뀌지 않습니다. signal.throwIfAborted()는 중단되었다면 저장된 reason을 그대로 던집니다. 따라서 중단은 한 통로로 처리하고 원인은 구분할 수 있습니다.
[따라하기]
아래 내용을 cancel-race.mjs로 저장합니다.
```js
const userController = new AbortController();
const timeoutSignal = AbortSignal.timeout(80);
const combined = AbortSignal.any([
userController.signal,
timeoutSignal,
]);
setTimeout(() => {
userController.abort(
new DOMException('사용자 취소', 'AbortError'),
);
}, 20);
await new Promise((resolve) => {
combined.addEventListener('abort', resolve, { once: true });
});
console.log(`aborted=${combined.aborted}`);
console.log(`reason=${combined.reason.name}`);
try {
combined.throwIfAborted();
} catch (error) {
console.log(`caught=${error.name}`);
}
```
터미널에서 node cancel-race.mjs를 실행합니다. Windows PowerShell, macOS Terminal, Linux 셸 모두 같은 명령입니다. 예상 결과는 다음과 같습니다.
```text
aborted=true
reason=AbortError
caught=AbortError
```
20밀리초의 사용자 취소가 80밀리초 제한보다 먼저 도착했습니다. Node.js v25.8.1에서 이 결과를 확인했습니다.
[흔한 실수]
combined.aborted만 보면 재시도 여부를 잘못 판단할 수 있으므로 reason.name도 확인하세요. 이미 중단된 신호에는 나중에 등록한 abort 이벤트가 다시 발생하지 않습니다. 외부 신호를 받으면 먼저 aborted를 확인하거나 시작점에서 throwIfAborted()를 호출하세요. 리스너에는 { once: true }를 써서 불필요한 참조가 남지 않게 합니다. 작업 코드가 신호를 무시하면 실제 작업은 멈추지 않는다는 점도 주의합니다.
[보안 주의]
시간 제한은 권한 검사나 자원 제한을 대신하지 않습니다. 큰 입력이 자원을 오래 점유하지 못하도록 요청 크기와 동시 실행 수도 제한하세요. 중단 이유에 토큰, 개인정보, 전체 요청 본문을 넣거나 로그로 남기지 않습니다. 실습은 본인 소유의 로컬 환경에서만 실행합니다.
[직접 해볼 과제]
사용자 취소 타이머를 20에서 120밀리초로 바꿔 실행하세요. 80밀리초 제한이 먼저 도착해 reason과 caught가 TimeoutError가 되는지 확인합니다. 이어 두 시간을 모두 80밀리초로 맞춰 여러 번 실행하고, 같은 시각을 기대하는 설계가 불안정한 이유를 적어 보세요.
[확인문제]
1. AbortSignal.timeout()이 중단될 때 reason.name은 무엇인가요?
2. 두 입력 신호가 차례로 중단되면 합친 신호에는 어느 이유가 남나요?
3. abort 이벤트 리스너에 { once: true }를 권장하는 이유는 무엇인가요?
[다음 학습]
다음에는 합친 신호를 fetch에 전달하고 정상 응답, 사용자 취소, 시간 초과, 서버 오류를 구분해 처리합니다.
[공식 참고 자료]
- WHATWG DOM Standard — AbortSignal: https://dom.spec.whatwg.org/#interface-AbortSignal
- WHATWG DOM Standard — AbortController: https://dom.spec.whatwg.org/#interface-abortcontroller
- Node.js Global objects — AbortSignal.timeout(): https://nodejs.org/api/globals.html#static-method-abortsignaltimeoutdelay
- Node.js Global objects — AbortSignal.any(): https://nodejs.org/api/globals.html#static-method-abortsignalanysignals
댓글목록 0
등록된 댓글이 없습니다.
