🔥 쿼리 취소

527자
5분

TanStack Query는 각 쿼리 함수에 AbortSignal 인스턴스를 제공합니다. 쿼리가 오래되거나 비활성화되면 이 신호가 중단됩니다. 이는 모든 쿼리를 취소할 수 있으며, 원한다면 쿼리 함수 내에서 취소에 대응할 수 있음을 의미합니다. 이 방식의 가장 큰 장점은 일반적인 async/await 문법을 그대로 사용하면서도 자동 취소의 모든 이점을 누릴 수 있다는 점입니다.

AbortController API는 대부분의 실행 환경에서 사용할 수 있습니다. 하지만 실행 환경이 이를 지원하지 않는다면 폴리필을 제공해야 합니다. 여러 가지 폴리필을 사용할 수 있습니다.

기본 동작

기본적으로 프로미스가 해결되기 전에 언마운트되거나 사용되지 않게 된 쿼리는 취소되지 않습니다. 이는 프로미스가 해결된 후에도 결과 데이터를 캐시에서 사용할 수 있음을 의미합니다. 쿼리 수신을 시작했지만 완료되기 전에 컴포넌트를 언마운트한 경우에 유용합니다. 컴포넌트를 다시 마운트하고 쿼리가 아직 가비지 컬렉션되지 않았다면 데이터를 사용할 수 있습니다.

그러나 AbortSignal을 사용하면 프로미스가 취소됩니다(예: fetch 중단). 따라서 쿼리도 취소해야 합니다. 쿼리를 취소하면 상태가 이전 상태로 되돌아갑니다.

fetch 사용하기

const query = useQuery({
  queryKey: ['todos'],
  queryFn: async ({ signal }) => {
    const todosResponse = await fetch('/todos', {
      // 하나의 fetch에 신호 전달
      signal,
    })
    const todos = await todosResponse.json()
 
    const todoDetails = todos.map(async ({ details }) => {
      const response = await fetch(details, {
        // 또는 여러 개에 전달
        signal,
      })
      return response.json()
    })
 
    return Promise.all(todoDetails)
  },
})
 
typescript

axios 사용하기 (v0.22.0 이상)

import axios from 'axios'
 
const query = useQuery({
  queryKey: ['todos'],
  queryFn: ({ signal }) =>
    axios.get('/todos', {
      // `axios`에 신호 전달
      signal,
    }),
})
 
typescript

v0.22.0 미만의 axios 사용하기

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
  },
})
 
typescript

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()
    })
  },
})
 
typescript

graphql-request 사용하기

클라이언트 요청 메서드에서 AbortSignal을 설정할 수 있습니다.

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

v4.0.0 미만의 graphql-request 사용하기

GraphQLClient 생성자에서 AbortSignal을 설정할 수 있습니다.

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

수동 취소

때로는 쿼리를 수동으로 취소하고 싶을 수 있습니다. 예를 들어, 요청이 완료되는 데 시간이 오래 걸리는 경우 사용자가 취소 버튼을 클릭하여 요청을 중지할 수 있게 할 수 있습니다. 이를 위해 queryClient.cancelQueries({ queryKey })를 호출하면 됩니다. 이 호출은 쿼리를 취소하고 이전 상태로 되돌립니다. 쿼리 함수에 전달된 신호를 사용했다면 TanStack Query는 추가로 프로미스도 취소합니다.

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'] })
    }}
  >
    취소
  </button>
)
 
typescript