[GraphQL] ObservableQuery with this id doesn't exist

2020. 12. 18. 00:15Trouble Shooting

728x90

Apollo Graphql Client 모듈을 사용하던 중 다음과 같은 오류를 접하는 경우가 종종 있습니다.

ObservableQuery with this id doesn't exist

위 오류는 React Hooks 또는 Vue/Nuxt의 @vue/apollo-composable이나, @nuxtjs/apollo 등의 apollo client를 사용하는 모든 모듈에서 제공되는 fetchMore페이지 네이션을 쉽게 하기 위한 graphql client의 기능을 사용하던 중 발생하는 오류입니다.

 

하지만 지금 말씀드린 내용은 어디서 발생하는지를 언급한 것이며, 원인은 이러한 이유 때문은 아닙니다.

즉 언제나 그렇듯 모듈이 문제가 아니라, 사람! 사용자가 문제라는 것이죠.

 

 

 

 

 

 

 

 

 

Dirty Solution

 

다른 사람들은 이를 해결하기 위한 방법으로 github issue에서 다음과 같은 방법을 언급하곤 합니다.

github.com/apollographql/apollo-client/issues/4114#issuecomment-502111099

 

'ObservableQuery with this id doesn't exist: id' on unmounted component · Issue #4114 · apollographql/apollo-client

Actual outcome: Hello, when any of my react native componets start fetching data and is unmounted befor finish, app yell : Possible Unhandled Promise Rejection (id: 0): Error: ObservableQuery with ...

github.com

위 링크 및 이미지에서 설명하는 방법은 try catch로 감싸고 await async로 호출 응답하여, 에러를 무시하라는 내용입니다. 저것은 단순하게는 작동하는 것처럼 보여도 만약에 previousResult 즉 이전 결과물을 버릴 거라면 사용해도 되는 방법입니다.

이 방법은 디버깅을 열심히 삽질하는 도중 이상한 점을 발견했는데 updateQuery에 가기도 전에 catch를 타버립니다.

때문에 위 방식은 updateQuery에 구구절절 열심히 써놔도 그 결과물이 무용지물입니다. 그래서 오로지 쿼리에서 들어온 결괏값만을 return 하게 되는 방식이죠

 

 

 

 

Clean Solution

 

아직까지 Google을 통해서는 해결방법이 나와 있지는 않습니다. 하지만 오늘 해보니 바로 원인을 찾았습니다.

'ObservableQuery with this id doesn't exist' 이 오류의 가장 큰 문제는 variables입니다.

 

github.com/vuejs/vue-apollo/issues/636#issuecomment-497658710

 

FetchMore calling backend twice and ObservableQuery with this id doesn't exist: 7 · Issue #636 · vuejs/vue-apollo

Hello, I'm doing this project to learn a bit more about graphQL But during the implementation of FetchMore, I'm struggling with this error: Uncaught (in promise) Invariant Violation: Observ...

github.com

해당 이슈 코멘트가 가장 결정적인 힌트였는데, 너무 단순하게 오류를 놓쳐버렸습니다.

 

 

아마 대다수의 오류를 발생시킨 유저들은 다음과 같은 방식으로 쿼리를 호출할 것입니다.

 

// example.vue

export default {
  setup() {
    const variables = reactive({
      page: 1,
      limit: 10,
    })
    const { result, fetchMore } = useQuery(GQL_QUERY, variables)
  }
}

이런 방식으로 호출할 경우 첫 쿼리에서 variables에 reactive, ref로 올 경우 바로 문제가 생겨버립니다.

그도 그럴 것이, fetchMore는 Client에서 호출이 되는 반면에,

setup에서 발생되는 useQuery는 Server에서 요청을 보냅니다. 정말 암덩어리죠.

그러다 보니 자세히 더 까 보시면 처음에 빈 값이 등장하고, 나중에 채워지는 걸 보면 query가 2번이 나가는 것 같은 모습을 볼 수 있었습니다...!

 

 

네 삽 소리입니다.

정확히는 useQuery를 까 보시면 되는데, Typescript로 OperationVariables로 선언이 되어있습니다.

이게 무슨 또 쌉소리냐면, 우리는 reactive 또는 ref 타입으로 전달을 하니깐 지는 Object 한 Key-Value 형식을 받는데 자꾸 이상한 거 집어넣어서 생긴 오류입니다.

 

해결 방법은 매우 간단합니다.

 

// example.vue

export default {
  setup() {
    const variables = reactive({
      page: 1,
      limit: 10,
    })
    const { result, fetchMore } = useQuery(GQL_QUERY, {...variables})
    const more = async () => {
    	variables.page++
        return await fetchMore({
        	variables: {...variables}
            updateQuery: (previouseResult, { fetchMoreResult }) => {
              if (!fetchMoreResult) return previousResult
              return {
                // your update query 아시죠?
              }
            }
        })
    }
    
    return {
    	result
        more,
	}
  }
}

... spread operator로 object 화해서 집어넣으면 됩니다.

JS의 Spread Operator는 python의 **이라고 생각하시면 됩니다.