14. 자바스크립트에서 프로미스를 취소하는 방법

https://velog.io/@eunbinn/cancel-promises-javascript?utm_source=substack&utm_medium=email

1. Promise.withResolvers() 사용하기

const buildCancelableTask = <T> (asyncFn: ()=> Promise<T>)=> {
	let rejected = false;
	const { promise, resolve, reject } = Promise.withResolvers<T>();

	return {
		run:()=>{
			if(!rejected){
				asyncFn().then(resolve, reject)
			}
			return promsie;
		},
		cancel:()=>{
			rejected=true;
			reject(new Error("CanceledError"));
		}
	}
}

2. AbortController 사용하기

const buildCancelableTask = <T> (asyncFn:()=>Promiose<T>)=>{
	const abortController = new AbortController();
return {
	run: ()=>{
		new Promise()<T>((resolve, reject)=> {
			const cancelTask = () => reject(new Error('CanceledError'));
		});
		if(abortController.signal.aborted){
			cancelTask();
			return;
		}
		asyncFn().then(resolve, reject);
		abortController.signal.addEventListnenr("abort", cancelTask);
	},

   cancel: ()=> {
	   abortController.abort();
   }	
}

}

3. 순차적 비동기 처리 리액트 훅만들기

import { useCallback, useRef } from "react";

const buildCancelableFetch = <T>(
  requestFn: (signal: AbortSignal) => Promise<T>
) => {
  const abortController = new AbortController();

  return {
    run: () =>
      new Promise<T>((resolve, reject) => {
        if (abortController.signal.aborted) {
          reject(new Error("CanceledError"));
          return;
        }

        requestFn(abortController.signal).then(resolve, reject);
      }),

    cancel: () => {
      abortController.abort();
    },
  };
};

// 항상 최신값 참조 
function useLatest<T>(value: T) {
  const ref = useRef(value);
  ref.current = value;
  return ref;
}

export function useSequentialRequest<T>(
  requestFn: (signal: AbortSignal) => Promise<T>
) {
// 항상 최신의 requestFn 을 참조한다.
  const requestFnRef = useLatest(requestFn);
  // 현재 진행중인 요청을 추적한다.
  const currentRequest = useRef<{ cancel: () => void } | null>(null);

  return useCallback(async () => {
// 1. 새 요청 시작시, 진행중인 요청이 있다면 취소한다.
    if (currentRequest.current) {
      currentRequest.current.cancel();
    }
// 2. 새 요청을 생성한다.
    const { run, cancel } = buildCancelableFetch(requestFnRef.current);
// 3. 현재 요청 정보를 업데이트한다.
    currentRequest.current = { cancel };

// 4. 요청 완료 후, 현재 요청이 완료된 요청과 동일한 경우 currentrequest 를 null로 설정한다.
    return run().finally(() => {
      if (currentRequest.current?.cancel === cancel) {
        currentRequest.current = null;
      }
    });
  }, []);
}

사용 예시

const fetchData = useSequentialRequest(async (signal) => {
  const response = await fetch('https://api.example.com/data', { signal });
  return response.json();
});

// 사용
useEffect(() => {
  fetchData().then(data => console.log(data));
}, [fetchData]);