- 자바스크립트는 Promise를 취소하는 API를 제공하지 않기 때문에 다른 방법으로 결과를 무시하는 방법을 소개합니다.
1. Promise.withResolvers()
- Promise.withResolver는 2024년에 새로 등장했습니다.
- 기존에는 Promise를 활용한다면 아래처럼 반드시 Resolve 함수와 Reject 함수를 인자로 받는 Callback 함수를 전달해야 했습니다.
let resolve, reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
const { promise, resolve, reject } = Promise.withResolvers();
- 아래와 같이 비동기 함수를 전달받아 실행과 취소를 할 수 있는 함수를 만들 수 있습니다.
const buildCancelablePromise = <T>(asyncFn: () => Promise<T>) => {
let rejected = false;
const { promise, resolve, reject } = Promise.withResolvers<T>();
return {
run: () => {
if (!rejected) {
asyncFn().then(resolve, reject);
}
return promise;
},
cancel: () => {
rejected = true;
reject(new Error("CanceledError"));
},
};
};
- 콜백으로 전달한 비동기 함수는 실행되었으나 완료되기 전 미리
cancel
를 통해 취소할 수 있습니다.
const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms));
const newPromise = buildCancelablePromise(async () => {
await sleep(2000);
return "resolve";
});
(async () => {
try {
const value = await newPromise.run();
console.log("value: ", value);
} catch (err) {
console.log("err: ", err);
}
})();
setTimeout(() => {
newPromise.cancel();
}, 500);
2. AbortController
- AbortController는 비동기 요청을 중단할 수 있는 기능을 제공합니다.
- 이 기능을 활용하여 아래처럼 중간에 요청을 취소할 수 있으며
cancel
을 많이 호출해도 abort
이벤트가 한번만 동작한다는 특징을 이용해 최신 데이터를 가져올 때 자주 사용한다고 합니다. ( GET 요청에 용이 )
const buildCancelablePromise = <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();
},
};
};
const newPromise = buildCancelablePromise(async (signal) => {
return fetch("http://localhost:3000", { signal }).then((res) => res.data);
});
(async () => {
try {
const val = await newPromise.run();
console.log("value: ", value);
} catch (err) {
console.log("err: ", err);
}
})();
setTimeout(() => {
newPromise.cancel();
}, 500);
3. React Hook으로 요청을 순차적으로 처리하는 기능 만들어보기
import { useCallback, useRef } from "react";
function useSequentialRequest<Args extends unknown[], Data>(
requestFn: (signal: AbortSignal, ...args: Args) => Promise<Data>,
) {
const requestFnRef = useRef(requestFn);
const running = useRef(false);
const abortController = useRef<AbortController | null>(null);
return useCallback(
async (...args: Args) => {
if (running.current) {
abortController.current?.abort();
abortController.current = null;
}
running.current = true;
const controller = abortController.current ?? new AbortController();
abortController.current = controller;
return requestFnRef.current(controller.signal, ...args).finally(() => {
if (controller === abortController.current) {
running.current = false;
}
});
},
[requestFnRef],
);
}
import React, { useState } from "react";
import "./App.css";
import { useSequentialRequest } from "./useSequentialRequest";
function App() {
const [data, setData] = useState("");
const fetchData = useSequentialRequest(
async (signal: AbortSignal, query: string) =>
fetch(`api url?query=${query}`, { signal }).then(
(res) => res.data()
)
);
const handleInput = async (value: string) => {
try {
const res = await fetchData(value);
setData(res);
} catch {
console.log("error");
}
};
return (
<div>
<input
onChange={(e) => {
handleInput(e.target.value);
}}
/>
</div>
);
}
export default App;
참고
항상 좋은 글 감사합니다 🔥