-
AbortController 도입시도프론트엔드 2026. 6. 1. 20:57
프로젝트 문제점
- 항상 프로젝트에는 새로운 문제점이 생긴다. 👋

문제 정의
- 문제점은 클라이언트 측에서 블로그 글 생성 -> 취소 할때의 문제가 있었다.
문제 해결 시도하기
- 클라이에서 블로그 글 생성 -> 취소 할때의 문제가 뭔지를 우선 정의했다.
흐름은 다음과 같다.
1. 사용자가 블로그 글을 생성한다.
2. 갑자기 사용자가 블로그 글을 취소해버리거나 뒤로 가기를 눌러버린다.
3. 내가 구현한 코드에는 취소나 뒤로가기를 누르면 그대로 AI API 요청을 보낸다.- 위와 같은 문제를 나는 클라이언트 측에서 AbortController를 활용해서 비동기 작업을 취소해보도록 했다.

AbortController ?
- AbortController란 현재 진행중인 비동기 작업을 취소할수 있게 해주는 Web 표준 Api라고 한다.
const controller = new AbortController(); controller.signal // 취소 신호를 감지하는 객체 controller.abort() // 취소 신호를 발생시키는 함수- 사용법은 간단하게 new 키워드로 AbortController를 선언하면 된다.
Signal 객체는 취소 신호를 감지하는 객체이고, abort() 함수를 호출하면 그 즉시 비동기 작업이 취소가 된다고 한다.
일단 뭔지는 이해했는데 언제 주로 사용할까??
useEffect(() => { fetch("/api/gemini", { body: JSON.stringify(payload) }) .then(res => res.json()) .then(data => setArticle(data)); // 문제 발생 가능 }, [payload]); //AbortController 사용 useEffect(() => { const controller = new AbortController(); fetch("/api/gemini", { signal: controller.signal }); return () => controller.abort(); // 네트워크 요청 자체를 끊음 }, [payload]);- 위 코드를 봐보자 나는 현재 fetch 함수로 Next.js 서버로 gemini API로 요청을 보냈다. 이때 만약 유저가 뒤로가기를 누르거나 탭을 꺼버린다면 위 요청은 취소가 되어야 한다.
- 이때 AbortController를 사용한다. 흐름을 보면 아래와 같다.
- fetch는 Web 표준 API라 Signal을 보고 스스로 멈추는데 이를 활용해서 Payload의 값이 변경이 되면 CleanUp 함수가 먼저 실행되어 취소가 되도록 유도하는 방법이다.
fetch가 signal 감시 중 ↓ signal.aborted = true 감지 ↓ fetch 내부에서 AbortError throw ↓ 요청 중단해결방법( 2. 갑자기 사용자가 블로그 글을 취소해버리거나 뒤로 가기를 눌러버린다.)
- 위 개념을 알고 이제 문제를 해결해보고자 했다.
useEffect(() => { if (!payload) { setError("작성 정보가 없습니다."); setIsGenerating(false); return; } const controller = new AbortController(); setArticle(null); setError(""); setIsGenerating(true); const run = async () => { try { const res = await fetch(aiEndpoint(), { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), signal: controller.signal, }); if (!res.ok) { const json = (await res.json().catch(() => ({}))) as { error?: string; }; throw new Error(json.error ?? `HTTP ${res.status}`); } const parsed = (await res.json()) as ArticleGenerationResponse; setArticle({ title: parsed.title, content: String(parsed.content ?? ""), keywords: Array.isArray(parsed.keywords) ? parsed.keywords.map(String) : [], template: templateKey, }); } catch (err) { if (controller.signal.aborted) return; setError( err instanceof Error ? err.message : "글 생성에 실패했습니다." ); } finally { if (!controller.signal.aborted) { setIsGenerating(false); } } }; run(); return () => controller.abort(); }, [payload, templateKey]); return { article, error, isGenerating };- 코드가 길지만 컨트롤러만 우선 보면 aiEndPoint()함수는 gemini한테 블로그 글을 작성해서 나한테 보내달라고 하는 api 요청 함수인데 유저가 뒤로 가기를했을때 혹은 페이지를 나갔을때 clenaup함수를 사용해 다음과 같은 문제를 방지하고자 했다.
하지만 문제가 있었다. 연결이 사실 2개였던 것이다.
[브라우저] ──①──▶ [Next.js /api/gemini] ──②──▶ [Gemini]나는 ①번 연결만 끊으면 된다고 생각했지만, ②번 연결은 별개의 HTTP 요청이라 클라이언트가 abort()를 해도 Next.js → Gemini 호출은 그대로 진행되고 있었다.
즉 클라이언트가 나가도 서버는 Gemini 응답을 끝까지 기다리고 있는 상태였다.
또 다른 문제는 SDK는 AbortController를 지원하지 않는다는 점이었다. 나는 이를 해결하고자 fetch로 직접 호출을 시도했다.정리
AbortController는 신호를 보내는 것일 뿐 받는 쪽이 직접 처리해야 한다.
fetch는 signal을 내장 지원하지만 SDK는 지원하지 않을 수 있다.
연결이 여러 개라면 각 연결마다 signal을 직접 연결해줘야 한다.
'프론트엔드' 카테고리의 다른 글
프로젝트를 다방면으로 생각해보기 (0) 2026.06.14 클라이언트 중복 방어의 한계와 서버 이중 방어 — isSubmitting부터 idempotency key까지 (0) 2026.06.07 React Activity? (0) 2026.03.12 쿠키, 세션, 스토리지 (1) 2025.12.06 React 딥다이브 (0) 2025.10.20