Javascript의 Promise는 취소되지 않는다.

0
const [storeReviews, storeLikes] = await Promise.all([
  StoreReviewModel.find({ store: storeUUID, deletedAt: null }),
  StoreLikeModel.find({ store: storeUUID, deletedAt: null })
]);

저번 글에 작성했던 가게에 대한 정보와, 가게의 좋아요 목록을 가져오는 코드를 서로 영향을 주지 않기 때문에 promise.all을 통해 병렬처리 하여 가져왔다.

하지만 팀원분이 이 코드는 문제없이 동작하지만 Database의 조회 이외의 작업을 할때는 위험한 코드라고 말씀하셨다.

const [storeReviews, storeLikes] = await Promise.all([
  new StoreReviewModel({ user:1, review: "good" },
  new StoreLikeModel({ user:1, store:1 }
]);

예를 들어 위의 코드를 보자. 가게에 대한 리뷰를 작성하고, 좋아요도 늘려주었다. 위 두 작업은 연관성이 없어 Promise.all로 처리하였다. 하지만 만약 첫번째 가게에 대한 리뷰를 작성만 성공하고 좋아요를 늘리는 작업은 실패하면 어떻게 될까?

Promise.all은 전달되는 promise 중 하나라도 거부되면, Promise.all이 반환하는 promise는 에러와 함께 바로 거부된다고 하였다. 하지만 Javascript의 Promise는 취소되지 않기 때문에 에러를 return하는 것이지, 리뷰작성 쿼리는 그대로 계속 수행되어 Database에 저장되어 버리는 것이다.

왜 Javascript의 Promise는 취소되지 않을까?

Promise는 한 번 생성되면 일단 만들어진 상태로 유지된다. 한 번 만들어진 Promise는 취소되거나 중단되지 않고 완료될 때까지 실행되는 것이 보장된다. 이는 Promise의 불변성과 안정성을 보장하기 위해 고안된 설계 원칙이다.

따라서 JavaScript의 Promise는 취소 기능이 내장되어 있지 않습니다. 이는 Promise가 단순히 비동기 작업의 상태와 결과를 추상화하는 것에 초점을 맞추기 때문이다. 취소 기능이 필요한 경우에는 라이브러리나 패턴을 사용하여 비동기 작업을 취소할 수 있다.

왜 그럼 취소를 Promise에 내장시키지 않을까?

참조 - Promise는 왜 취소가 안 될까?

취소는 예외로 처리해야하는가?

만약 취소를 예외로 처리하게 되면 catch문 안에서 예외가 취소인지 아닌지 확인하는 것을 잊는다면 에러가 아님에도 에러로 처리되어 불필요한 정보를 에러 메시지로 보여주거나 서버 에러 로그에 남게 될 것이다.

 try {} catch cancel (reason) {} catch {}

try – catch cancel 등의 새로운 문법 요소를 도입하는 방법도 있다.
위 예제 코드에서 수행 중인 작업이 취소되면, catch 문이 아니라 catch cancel 문으로 점프한다. 만약 취소된 경우의 처리가 필요없다면 catch cancel 문은 생략할 수 있습다. 이 제안의 장점은 발생한 예외가 취소인지 아닌지 catch 문 안에서 매번 확인하지 않아도 된다는 점이다.

하지만 취소를 일반적인 예외와 다르게 취급할 경우, Promise에도 기존 then, catch, finally 등의 메소드와 더불어 취소를 위한 cancel, cancelCatch 등의 메소드를 추가해야 하고 결과적으로 논의 중인 스펙의 요구사항이 점점 복잡해지게 되어있다.

Promise를 사용하는 기존 코드에 대한 영향

이렇게 만약 Promise에 취소 기능이 생긴다면, Promise에 의존성이 있는 기존 코드에도 심각한 영향을 끼칠 수 있다.

Promise.allSettled를 이용한 안정성 보장

이렇게 모든 데이터의 안정성이 보장되어야 할때는 Promise.allSettled을 사용하면 된다. 저번 글에서 말했듯이 Promise.allSettled는 모든 프라미스가 처리될 때까지 기다린다. 또한 여러 요청 중 하나가 실패해도 다른 요청 결과는 여전히 유효하다.

const [storeReviews, storeLikes] = await Promise.all([
  new StoreReviewModel({ user:1, review: "good" },
  new StoreLikeModel({ user:1, store:1 }
]);
                       
if(storeReviews.status === 'rejected') { rollback; }
                                                     
if(storeLikes.status === 'rejected') { rollback; }                                                   

이렇게 rejected된 데이터들에 대해서는 rollback을 해주면 데이터의 안정성이 보장될 것이다.

profile
https://www.youtube.com/watch?v=__9qLP846JE

0개의 댓글