자바스크립트(JavaScript) Promise 취소, 불가능? 가능!

by DD
1개월 전
조회수 14

자바스크립트(JavaScript)의 Promise 취소 기능 부재(Lack of Cancellation)로 인한 문제점을 지적하며, TC39 위원회의 논의 중단 사실을 언급함

해결책으로 영원히 해결되지 않는 Promise(Unresolved Promise)를 활용하여 함수 실행을 중단하는 기법을 소개함

Inngest TypeScript SDK의 워크플로우(Workflow) 중단 및 재개 구현 사례를 통해 실용성을 강조함

AsyncLocalStorage를 활용한 Node.js 환경에서의 AbortSignal 구현 방식을 제시하며, 대안을 제시함

Promise 취소의 기술적 난제

게시물에서는 자바스크립트(JavaScript)에서 Promise를 취소하는 기능이 없는 근본적인 이유를 설명한다. 임의 코드 중단(Arbitrary Code Cancellation)은 열린 핸들(Open Handles)이나 부분적으로 쓰여진 데이터와 같은 자원 누수(Resource Leak)를 발생시킬 수 있다. 따라서 진정한 취소는 협력적인 정리(Cooperative Cleanup)를 필요로 하며, 이는 .cancel() 메서드가 제공하는 단순성을 해친다는 점을 지적한다.

영원히 해결되지 않는 Promise의 활용

저자는 Promise를 취소하는 대안으로, 영원히 해결되지 않는 Promise를 반환하는 방식을 제시한다. 이 기법은 함수 실행을 중단시키고, 가비지 컬렉터(Garbage Collector)가 해당 함수를 정리하도록 한다. 특히, 서버리스 환경에서 각 호출에 시간 제한이 있는 워크플로우(Workflow) 함수를 중단하고, 진행 상황을 저장한 후 다시 시작하는 Inngest SDK의 사례를 통해 실용성을 강조한다.

Generator와 Async/Await의 비교

게시물에서는 Generator를 사용한 취소 방식과 async/await 방식의 트레이드오프(Trade-off)를 분석한다. Generator는 각 yield 지점에서 실행을 일시 중지하고, 호출자가 재개 여부를 제어할 수 있어 깔끔한 중단이 가능하다. 하지만, yield는 동시성을 지원하지 않으며, 개발자가 function* 및 yield 구문을 사용해야 하는 불편함이 있다. 반면, async/await는 간결한 구문을 제공하지만, 호출자가 실행을 제어할 수 없다는 단점이 있다.

메모이제이션(Memoization)을 통한 워크플로우(Workflow) 재개

게시물에서는 여러 단계로 구성된 워크플로우(Workflow)를 중단하고 재개하는 방법을 설명한다. 핵심은 stepState라는 메모리 내 저장소를 사용하여 이미 실행된 단계의 결과를 저장하고, 새로운 단계가 실행될 때까지 함수 실행을 중단하는 것이다. setTimeout(0)을 사용하여 마이크로태스크(Microtask)가 먼저 완료되도록 보장하고, 가비지 컬렉션(Garbage Collection)을 통해 메모리 누수를 방지한다. 하지만, 가비지 컬렉션(Garbage Collection)의 비결정성(Nondeterministic)으로 인해, 참조 체인이 끊어지지 않으면 메모리 누수가 발생할 수 있다는 점을 주의해야 한다.

커뮤니티 반응: 기술적 딜레마와 대안

댓글에서는 제시된 기법이 다소 'dirty'하다는 의견과 함께, AsyncLocalStorage를 사용하여 AbortSignal을 관리하는 Node.js 기반의 대안이 제시되었다. 특히, AsyncLocalStorage를 사용하면 워크플로우(Workflow)를 중단하기 위해 신호를 전달할 필요 없이, 깔끔하게 처리할 수 있다. 또한, Promise가 프로그램 종료를 막지 못하는 동작 방식에 대한 이해 부족으로 인한 어려움을 토로하는 댓글도 존재한다.

You can't cancel a JavaScript promise (except sometimes you can)