Docs
GUIDES & CONCEPTS
Query Cancellation

Query Cancellation

TanStack Query는 각 쿼리 함수에 AbortSignal (opens in a new tab) 인스턴스를 제공합니다. 쿼리가 오래되었거나 비활성 상태가 되면 이 signal이 취소됩니다. 즉, 모든 쿼리는 취소할 수 있으며, 원하는 경우 쿼리 함수 내에서 취소에 응답할 수 있습니다. 이로 인해 일반적인 async/await 문법을 계속 사용할 수 있으면서 자동 취소의 모든 장점을 누릴 수 있습니다.

AbortController API는 대부분의 런타임 환경 (opens in a new tab)에서 사용 가능하지만, 런타임 환경이 이를 지원하지 않는 경우에는 폴리필을 제공해야 합니다. 여기서 여러 가지 폴리필을 찾을 수 있습니다 (opens in a new tab).

Default behavior

기본적으로, Promise가 해결되기 전에 언마운트되거나 사용되지 않게 된 쿼리는 취소되지 않습니다. 즉, Promise가 해결된 후에도 결과 데이터는 캐시에 남아 있습니다. 이는 쿼리를 수신하기 시작한 후, 컴포넌트를 언마운트했더라도 쿼리가 아직 가비지 컬렉션되지 않았다면 데이터가 여전히 사용 가능하다는 점에서 유용합니다.

하지만 AbortSignal을 사용하면 Promise가 취소되며 (예: fetch를 중단) 따라서 쿼리도 취소되어야 합니다. 쿼리를 취소하면 상태가 이전 상태로 되돌아갑니다.

Using fetch

const query = useQuery({
  queryKey: ["todos"],
  queryFn: async ({ signal }) => {
    const todosResponse = await fetch("/todos", {
      // signal을 fetch에 전달
      signal,
    });
    const todos = await todosResponse.json();
 
    const todoDetails = todos.map(async ({ details }) => {
      const response = await fetch(details, {
        // 여러 요청에 signal을 전달
        signal,
      });
      return response.json();
    });
 
    return Promise.all(todoDetails);
  },
});

Using axios v0.22.0+ (opens in a new tab)

import axios from "axios";
 
const query = useQuery({
  queryKey: ["todos"],
  queryFn: ({ signal }) =>
    axios.get("/todos", {
      // signal을 axios에 전달
      signal,
    }),
});

Using axios with version lower than v0.22.0

import axios from "axios";
 
const query = useQuery({
  queryKey: ["todos"],
  queryFn: ({ signal }) => {
    // 이 요청을 위한 새로운 CancelToken 소스 생성
    const CancelToken = axios.CancelToken;
    const source = CancelToken.source();
 
    const promise = axios.get("/todos", {
      // 요청에 소스 토큰 전달
      cancelToken: source.token,
    });
 
    // TanStack Query가 취소 신호를 보낼 경우 요청 취소
    signal?.addEventListener("abort", () => {
      source.cancel("쿼리가 TanStack Query에 의해 취소되었습니다.");
    });
 
    return promise;
  },
});

Using XMLHttpRequest

const query = useQuery({
  queryKey: ["todos"],
  queryFn: ({ signal }) => {
    return new Promise((resolve, reject) => {
      var oReq = new XMLHttpRequest();
      oReq.addEventListener("load", () => {
        resolve(JSON.parse(oReq.responseText));
      });
      signal?.addEventListener("abort", () => {
        oReq.abort();
        reject();
      });
      oReq.open("GET", "/todos");
      oReq.send();
    });
  },
});

Using graphql-request

AbortSignal은 클라이언트의 request 메서드에서 설정할 수 있습니다.

const client = new GraphQLClient(endpoint);
 
const query = useQuery({
  queryKey: ["todos"],
  queryFn: ({ signal }) => {
    client.request({ document: query, signal });
  },
});

Using graphql-request with version lower than v4.0.0

AbortSignalGraphQLClient 생성자에서 설정할 수 있습니다.

const query = useQuery({
  queryKey: ["todos"],
  queryFn: ({ signal }) => {
    const client = new GraphQLClient(endpoint, {
      signal,
    });
    return client.request(query, variables);
  },
});

Manual Cancellation

쿼리를 수동으로 취소해야 할 경우가 있을 수 있습니다. 예를 들어, 요청이 완료되는 데 시간이 오래 걸리는 경우, 사용자가 취소 버튼을 클릭하여 요청을 중단할 수 있도록 할 수 있습니다. 이를 위해서는 queryClient.cancelQueries({ queryKey })를 호출하면 됩니다. 이 메서드는 쿼리를 취소하고 이전 상태로 되돌립니다. signal을 쿼리 함수에서 사용한 경우, TanStack Query는 Promise도 추가로 취소합니다.

const query = useQuery({
  queryKey: ["todos"],
  queryFn: async ({ signal }) => {
    const resp = await fetch("/todos", { signal });
    return resp.json();
  },
});
 
const queryClient = useQueryClient();
 
return (
  <button
    onClick={(e) => {
      e.preventDefault();
      queryClient.cancelQueries({ queryKey: ["todos"] });
    }}
  >
    Cancel
  </button>
);