티스토리 뷰

리액트에서는 컴포넌트가 언마운트된 상태에서 setState() 가 호출되면 워닝을 표시한다. 언마운트된 컴포넌트에 setState()를 호출하는 것은 클린 업이 제대로 안돼서 컴포넌트가 언마운트된 다음에도 앱이 그 컴포넌트에 대한 참조를 가지고 있다는 뜻이고, 이는 메모리 누수를 일으킬 가능성이 있기 때문이다.

그래서 예전에는 지금은 deprecated된 isMounted() 로 마운트됐는지 조건을 체크한 다음 setState()를 호출하게 했는데, 이게 바람직하지 않은 이유는 워닝을 받아야 할 때에도 워닝을 받지 못할 경우가 생기기 때문이다.

그래서 _isMounted라는 프로퍼티를 만들어서 componentDidMount 에서 _isMounted를 true로 설정하고 componentWillUnmount에서 false로 설정하는 방법이 있기도 했는데, 이런 방법들보다는 애초에 문제가 일어나지 않게 방지하는 것이 더 바람직하다. 즉, 언마운트된 컴포넌트에서 setState가 호출될 만한 곳을 미리 파악해서 고치는 것이다.

예를 들어 가장 흔하게 이런 문제가 발생하는 경우가 API 호출을 해서 데이터를 기다리고 있는 와중에 컴포넌트가 언마운트되고 나중에 콜백이 실행돼서 이미 언마운트된 컴포넌트에 setState를 하려고 하는 상황이다.

이런 경우를 막기 위해 cancellable한 promise를 만들어볼 수 있다.

const makeCancelable = (promise) => {
  let hasCanceled_ = false;

  const wrappedPromise = new Promise((resolve, reject) => {
    promise.then(
           val => hasCanceled_ ? reject({isCanceled: true}) : resolve(val),
      error => hasCanceled_ ? reject({isCanceled: true}) : reject(error)
    )
  });
  return {
    promise: wrappedPromise,
    cancel() {
      hasCanceled_ = true;
    },
  };
};
const cancelablePromise = makeCancelable(
    new Promise(r => component.setState({ ... }))
);

cancelablePromise
  .promise
  .then(() => console.log('resolved'))
    .catch((reason) => console.log('isCanceled', reason.isCanceled));

cancelablePromise.cancel(); // Cancel the promise
  • 프로미스 객체를 makeCancelable 함수에 넘겨서 cancelablePromise로 래핑한 다음,
  • 필요할 때 cancel 메서드를 호출하면(e.g. 컴포넌트 언마운트 직전에), 내부적으로 hasCanceled_ 변수가 true로 설정될 것이고,
  • 그러면 아규먼트로 넘겼던 프로미스가 성공하든, 실패하든 hasCanceled_ 는 true라 wrappedPromise는 reject를 call할 것이고, 그러면 catch문으로 들어가서 에러를 핸들링할 수 있을 것이다.

아니면 조금 더 다른 패턴으로는 이렇게 만들 수도 있다. 원래 resolve, reject는 Promise 생성자에 포함된 내부 함수지만, 그것을 바깥에 선언한 변수에 할당해서(클로저로 만들어서) 바깥에서 쉽게 resolve, reject하는 것이다. 여기서는 cancel메서드를 호출하면 reject가 호출되도록 만들어 두었다.

const makeCancelable = (promise) => {
  let _reject;
  const wrappedPromise = new Promise((resolve, reject) => {
    _reject = reject;
    promise.then(resolve, reject);
  });
  return {
    promise: wrappedPromise,
    cancel(message = 'promise is canceled') {
      _reject(new Error(message));
    },
  };
};

Ref

https://reactjs.org/blog/2015/12/16/ismounted-antipattern.html

https://stackoverflow.com/questions/26150232/resolve-javascript-promise-outside-function-scope

https://javascript.info/promise-basics

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함