apollo-client: Error Observable cancelled prematurely get's thrown

Sorry that I cannot provide more info. I may get some time to try and reproduce this in codesandbox in the future, but for now I just wanted to have a linkable bug reference to put into our codebase. For now we have this hackfix in ErrorBoundary component to get around this:


  componentDidCatch(error: Error) {
    captureException(error)

    if (error.message === 'Observable cancelled prematurely') {
      console.warn(error) // this is an annoying error getting thrown by apollo client 3.3.x. Some future versions might be okay, but it's the safest to leave this here
      // easiest to get this is to use login link on a user profile page and open it in incognito window, There it happens 100% of the times
      location.reload() // slightly hacky, but we can't think of a better hotfix ATM
      return
    }
    this.setState({ error })
  }

Intended outcome: no error is thrown no matter how we mount/dismount providers. If this indeed happens when a provider is unmounted by react then it should be caught by apollo-client because at that point we really don’t care about these observables and their values at all.

Actual outcome: this error is thrown:

Uncaught Error: Observable cancelled prematurely
  | ApolloError | @ | index.js:26
  | (anonymous) | @ | QueryManager.js:535
  | (anonymous) | @ | asyncMap.js:11
  | (anonymous) | @ | asyncMap.js:11
  | notifySubscription | @ | Observable.js:140
  | flushSubscription | @ | Observable.js:121
  | (anonymous) | @ | Observable.js:174
  | (anonymous) | @ | Observable.js:73
  | Promise.then (async) |   |  
  | onNewData | @ | useBaseQuery.js:18
  | error | @ | QueryData.js:240
  | notifySubscription | @ | Observable.js:140
  | onNotify | @ | Observable.js:179
  | error | @ | Observable.js:240
  | (anonymous) | @ | iteration.js:4
  | iterateObserversSafely | @ | iteration.js:4
  | error | @ | ObservableQuery.js:26
  | (anonymous) | @ | iteration.js:4
  | iterateObserversSafely | @ | iteration.js:4
  | error | @ | Concast.js:35
  | notifySubscription | @ | Observable.js:140
  | onNotify | @ | Observable.js:179
  | error | @ | Observable.js:240
  | (anonymous) | @ | asyncMap.js:19
  | Promise.then (async) |   |  
  | (anonymous) | @ | asyncMap.js:11
  | notifySubscription | @ | Observable.js:140
  | flushSubscription | @ | Observable.js:121
  | (anonymous) | @ | Observable.js:174
  | (anonymous) | @ | Observable.js:73
  | Promise.then (async) |   |  
  | enqueue | @ | Observable.js:71
  | onNotify | @ | Observable.js:173
  | error | @ | Observable.js:240
  | ../node_modules/@apollo/client/utilities/observables/Concast.js.Concast.deliverLastMessage | @ | Concast.js:86
  | ../node_modules/@apollo/client/utilities/observables/Concast.js.Concast.addObserver | @ | Concast.js:97
  | (anonymous) | @ | Concast.js:11
  | Subscription | @ | Observable.js:197
  | subscribe | @ | Observable.js:279
  | (anonymous) | @ | asyncMap.js:37
  | Subscription | @ | Observable.js:197
  | subscribe | @ | Observable.js:279
  | complete | @ | Concast.js:56
  | notifySubscription | @ | Observable.js:145
  | onNotify | @ | Observable.js:179
  | complete | @ | Observable.js:245
  | (anonymous) | @ | Observable.js:593
  | (anonymous) | @ | Observable.js:73
  | Promise.then (async) |   |  
  | enqueue | @ | Observable.js:71
  | (anonymous) | @ | Observable.js:585
  | Subscription | @ | Observable.js:197
  | subscribe | @ | Observable.js:279
  | complete | @ | Concast.js:56
  | ../node_modules/@apollo/client/utilities/observables/Concast.js.Concast.start | @ | Concast.js:79
  | Concast | @ | Concast.js:71
  | ../node_modules/@apollo/client/core/QueryManager.js.QueryManager.fetchQueryObservable | @ | QueryManager.js:580
  | (anonymous) | @ | ObservableQuery.js:312
  | ../node_modules/@apollo/client/core/Reobserver.js.Reobserver.reobserve | @ | Reobserver.js:18
  | ../node_modules/@apollo/client/core/ObservableQuery.js.ObservableQuery.reobserve | @ | ObservableQuery.js:317
  | ../node_modules/@apollo/client/core/ObservableQuery.js.ObservableQuery.onSubscribe | @ | ObservableQuery.js:294
  | (anonymous) | @ | ObservableQuery.js:13
  | Subscription | @ | Observable.js:197
  | subscribe | @ | Observable.js:279
  | ../node_modules/@apollo/client/react/data/QueryData.js.QueryData.startQuerySubscription | @ | QueryData.js:220
  | ../node_modules/@apollo/client/react/data/QueryData.js.QueryData.getExecuteResult | @ | QueryData.js:150
  | ../node_modules/@apollo/client/react/data/QueryData.js.QueryData.execute | @ | QueryData.js:98
  | (anonymous) | @ | useBaseQuery.js:35
  | useDeepMemo | @ | useDeepMemo.js:6
  | useBaseQuery | @ | useBaseQuery.js:35
  | useQuery | @ | useQuery.js:3
  | useThrowingQuery | @ | useThrowingQuery.ts:18
  | useLoginPageQuery | @ | LoginPage.codegen.tsx:157
  | OauthLoginButtons | @ | OauthLoginButtons.tsx:36
  | renderWithHooks | @ | react-dom.development.js:14985
  | mountIndeterminateComponent | @ | react-dom.development.js:17811
  | beginWork | @ | react-dom.development.js:19049
  | beginWork$1 | @ | react-dom.development.js:23940
  | performUnitOfWork | @ | react-dom.development.js:22776
  | workLoopSync | @ | react-dom.development.js:22707
  | renderRootSync | @ | react-dom.development.js:22670
  | performSyncWorkOnRoot | @ | react-dom.development.js:22293
  | (anonymous) | @ | react-dom.development.js:11327
  | unstable_runWithPriority | @ | scheduler.development.js:646
  | runWithPriority$1 | @ | react-dom.development.js:11276
  | flushSyncCallbackQueueImpl | @ | react-dom.development.js:11322
  | flushSyncCallbackQueue | @ | react-dom.development.js:11309
  | scheduleUpdateOnFiber | @ | react-dom.development.js:21893
  | dispatchAction | @ | react-dom.development.js:16139
  | Promise.then (async) |   |  
  | onNewData | @ | useBaseQuery.js:18
  | next | @ | QueryData.js:230
  | notifySubscription | @ | Observable.js:135
  | onNotify | @ | Observable.js:179
  | next | @ | Observable.js:235
  | (anonymous) | @ | iteration.js:4
  | iterateObserversSafely | @ | iteration.js:4
  | next | @ | ObservableQuery.js:21
  | (anonymous) | @ | iteration.js:4
  | iterateObserversSafely | @ | iteration.js:4
  | next | @ | Concast.js:24
  | notifySubscription | @ | Observable.js:135
  | onNotify | @ | Observable.js:179
  | next | @ | Observable.js:235
  | (anonymous) | @ | Observable.js:589
  | (anonymous) | @ | Observable.js:73
  | Promise.then (async) |   |  
  | enqueue | @ | Observable.js:71
  | (anonymous) | @ | Observable.js:585
  | Subscription | @ | Observable.js:197
  | subscribe | @ | Observable.js:279
  | complete | @ | Concast.js:56
  | ../node_modules/@apollo/client/utilities/observables/Concast.js.Concast.start | @ | Concast.js:79
  | Concast | @ | Concast.js:71
  | ../node_modules/@apollo/client/core/QueryManager.js.QueryManager.fetchQueryObservable | @ | QueryManager.js:580
  | (anonymous) | @ | ObservableQuery.js:312
  | ../node_modules/@apollo/client/core/Reobserver.js.Reobserver.reobserve | @ | Reobserver.js:18
  | ../node_modules/@apollo/client/core/ObservableQuery.js.ObservableQuery.reobserve | @ | ObservableQuery.js:317
  | listeners.add.oqListener | @ | QueryInfo.js:103
  | (anonymous) | @ | QueryInfo.js:115
  | ../node_modules/@apollo/client/core/QueryInfo.js.QueryInfo.notify | @ | QueryInfo.js:115
  | (anonymous) | @ | QueryManager.js:447
  | ../node_modules/@apollo/client/core/QueryManager.js.QueryManager.broadcastQueries | @ | QueryManager.js:447
  | ../node_modules/@apollo/client/core/QueryManager.js.QueryManager.stopQuery | @ | QueryManager.js:433
  | (anonymous) | @ | QueryManager.js:332
  | Promise.finally (async) |   |  
  | ../node_modules/@apollo/client/core/QueryManager.js.QueryManager.query | @ | QueryManager.js:332
  | ../node_modules/@apollo/client/core/ApolloClient.js.ApolloClient.query | @ | ApolloClient.js:133
  | (anonymous) | @ | loadCustomFonts.ts:113
  | (anonymous) | @ | loadCustomFonts.codegen.tsx:64
  | __async | @ | loadCustomFonts.codegen.tsx:64
  | loadCustomFonts | @ | loadCustomFonts.ts:112
  | transitionAppStateToLoggedIn | @ | sessionStore.ts:289
  | AuthSuccess | @ | AuthSuccess.tsx:34
  | renderWithHooks | @ | react-dom.development.js:14985
  | updateFunctionComponent | @ | react-dom.development.js:17356
  | beginWork | @ | react-dom.development.js:19063
  | beginWork$1 | @ | react-dom.development.js:23940
  | performUnitOfWork | @ | react-dom.development.js:22776
  | workLoopSync | @ | react-dom.development.js:22707
  | renderRootSync | @ | react-dom.development.js:22670
  | performSyncWorkOnRoot | @ | react-dom.development.js:22293
  | (anonymous) | @ | react-dom.development.js:11327
  | unstable_runWithPriority | @ | scheduler.development.js:646
  | runWithPriority$1 | @ | react-dom.development.js:11276
  | flushSyncCallbackQueueImpl | @ | react-dom.development.js:11322
  | flushSyncCallbackQueue | @ | react-dom.development.js:11309
  | scheduleUpdateOnFiber | @ | react-dom.development.js:21893
  | dispatchAction | @ | react-dom.development.js:16139
  | Promise.then (async) |   |  
  | onNewData | @ | useBaseQuery.js:18
  | next | @ | QueryData.js:230
  | notifySubscription | @ | Observable.js:135
  | onNotify | @ | Observable.js:179
  | next | @ | Observable.js:235
  | (anonymous) | @ | iteration.js:4
  | iterateObserversSafely | @ | iteration.js:4
  | next | @ | ObservableQuery.js:21
  | (anonymous) | @ | iteration.js:4
  | iterateObserversSafely | @ | iteration.js:4
  | next | @ | Concast.js:24
  | notifySubscription | @ | Observable.js:135
  | onNotify | @ | Observable.js:179
  | next | @ | Observable.js:235
  | (anonymous) | @ | asyncMap.js:13
  | Promise.then (async) |   |  
  | (anonymous) | @ | asyncMap.js:11
  | notifySubscription | @ | Observable.js:135
  | onNotify | @ | Observable.js:179
  | next | @ | Observable.js:235
  | (anonymous) | @ | iteration.js:4
  | iterateObserversSafely | @ | iteration.js:4
  | next | @ | Concast.js:24
  | notifySubscription | @ | Observable.js:135
  | onNotify | @ | Observable.js:179
  | next | @ | Observable.js:235
  | next | @ | bundle.esm.js:29
  | notifySubscription | @ | Observable.js:135
  | onNotify | @ | Observable.js:179
  | next | @ | Observable.js:235
  | (anonymous) | @ | batching.js:84
  | (anonymous) | @ | batching.js:84
  | next | @ | batching.js:82
  | notifySubscription | @ | Observable.js:135
  | onNotify | @ | Observable.js:179
  | next | @ | Observable.js:235
  | (anonymous) | @ | batchHttpLink.js:75
  | Promise.then (async) |   |  
  | (anonymous) | @ | batchHttpLink.js:74
  | Subscription | @ | Observable.js:197
  | subscribe | @ | Observable.js:279
  | ../node_modules/@apollo/client/link/batch/batching.js.OperationBatcher.consumeQueue | @ | batching.js:72
  | (anonymous) | @ | batching.js:105


How to reproduce the issue: I actually don’t know. This error is thrown on startup of our closed source app, so I cannot share the code. The app actually has 2 graphQL endpoints and we switch between them depending on the route where the user is and I think this error get’s thrown when one apollo provider is unmounted, but I cannot be sure.

Versions

  System:
    OS: Linux 5.4 Ubuntu 20.04.1 LTS (Focal Fossa)
  Binaries:
    Node: 12.18.4 - ~/.nvm/versions/node/v12.18.4/bin/node
    Yarn: 1.22.4 - ~/.yarn/bin/yarn
    npm: 6.14.6 - ~/.nvm/versions/node/v12.18.4/bin/npm
  Browsers:
    Chrome: 87.0.4280.141
    Firefox: 84.0.2
  npmPackages:
    @apollo/client: ^3.3.7 => 3.3.7 
    @apollo/link-batch-http: ^2.0.0-beta.3 => 2.0.0-beta.3 
    @apollo/link-context: ^2.0.0-beta.3 => 2.0.0-beta.3 
    @apollo/link-error: ^2.0.0-beta.3 => 2.0.0-beta.3 
    @apollo/link-schema: ^2.0.0-beta.3 => 2.0.0-beta.3 
    @apollo/react-components: ^3.1.5 => 3.1.5 
    @apollo/react-testing: ^3.1.4 => 3.1.4 
    apollo-client: ^2.6.10 => 2.6.10 
    apollo-server: ^2.19.2 => 2.19.2 
    apollo-server-express: ^2.19.2 => 2.19.2 
    react-apollo-network-status: ^5.0.1 => 5.0.1 

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 24
  • Comments: 68 (14 by maintainers)

Commits related to this issue

Most upvoted comments

For us, it happens when you unmount and remount the sasme component really fast.

Experiencing the same thing in an Angular project, when navigating away from a page where a query is happening.

Any progress? This issue is a huge problem for me.

If it helps, I started getting this error after updating @apollo/client 3.3.21 => 3.4.0 and still get it on the latest version 3.4.5.

Thanks @capaj for posting your workaround, very useful.

To the Apollo folks, it seems to me like throwing an error and breaking the user’s app is a bit extreme for this type of issue (especially since it appear to only occur in production mode).

I have no knowledge of this codebase, but could this line not pass true as a second parameter to prevent throwing errors? Or are errors being thrown as a method of communication, and accidentally being allowed out into userland?

eagerly waiting for a fix too…

I noticed this starting to happen in my Angular app. It generally happens on sign in. You immediately get booted out of the app and it throws the “Observable cancelled prematurely” message. Unfortunately, we cannot reproduce with certainty so it’s been a nightmare to try and replicate, much less fix.

Can also confirm that 3.7.0-alpha.3 fixed the Observable cancelled prematurely issue for me too.

I am experiencing same issue. This had started with version Apollo Client 3.3.0. For now I had downgraded to Apollo Client 3.2.9. . I think that this issue is connected with this feature “Unsubscribing the last observer from an ObservableQuery will once again unsubscribe from the underlying network Observable in all cases, as in Apollo Client 2.x, allowing network requests to be cancelled by unsubscribing.” Witch was implemented in Apollo Client 3.3.0

@benjamn Thanks for your work, but the problem is still not solved, and it’s worse in the new versions, because before you could at least see the error and know that something was wrong, but now not even that. Could you re-add the error message? I have a temporary fix using the error

I’m cautiously optimistic this problem may be solved once and for all by #9701, which you can test by running

npm i @apollo/client@beta

to get version 3.7.0-alpha.3. If all goes well, these changes should be safe to release in 3.6.4, not waiting from v3.7, so please let us know if/when you have a chance to test the alpha!

Did some digging and found something quite interesting:

We recently upgraded to use react 18.0.0, using @apollo/client 3.6.1. One interesting thing with react 18 is its new development-only check in StrictMode. It

will automatically unmount and remount every component, whenever a component mounts for the first time, restoring the previous state on the second mount.

Reading the whole linked paragraph, it also says that this mount/unmount behavior will be a performance optimization feature and that it

requires components to be resilient to effects being mounted and destroyed multiple times

This means that this will be a future problem in non-development react apps as well.

Since we use StrictMode for our app, this new mount/unmount behavior causes the problem for us for our useSubscription hooks. When I remove StrictMode, the subscription worked like a charm again! However, aside from debugging, that’s not an option because a) we want to use StrictMode and b) as described above, this behavior is becoming a feature in react 18.

@brainkim Do you already have a suitable repo for reproduction?

This issue could get fixed in the refactoring happening in #9331. But we’ll need a sign from core team for that PR to progress further.

I was having this problem, I read and research several things about that issue. Unfortunately, the issue is specific to your approach and codebase. You may have a memory leak or unexpected behavior on your product as a code. Already it proofs that because all these comments are pointing to different complaints.

@myth535 I have the same problem, the useSubscription sometimes works and sometimes don’t, feels unstable. I have to implement my own vanilla javascript subscription:

useEffect(() => {
    const client = backend.apolloClient;
    const susbObservable = client.subscribe({ query: gSubs, variables: vars });
    const subscription = susbObservable.subscribe(
      //onNext
      (value) => {
        if (get(value, 'data.Conversation.key') === conversationKey) {
          setConversation(value.data.Conversation);
        }
      },
      //onError
      (error) => {
        logger.error(conversationKey, error);
      }
    );

    return () => {
      subscription.unsubscribe();
    };
  }, [conversationKey]);

The subscription feels more stable and works well!

We believe this is now resolved in @apollo/client@3.6.4 and later - let us know otherwise. Thanks!

I’m cautiously optimistic this problem may be solved once and for all by #9701, which you can test by running

npm i @apollo/client@beta

to get version 3.7.0-alpha.3. If all goes well, these changes should be safe to release in 3.6.4, not waiting from v3.7, so please let us know if/when you have a chance to test the alpha!

3.7.0-alpha.3 resolved the issue for me! Was stuck on v3.2.9 only because the later versions had this issue. Thanks for the fix, hope it is merged in the stable release soon.

I am seeing this issue while using subscription with react-table.

I was facing this issue using useSubsrciption hook. Was using useSubscription hook into a component which was mounting and then unmounting.

Figured out the unmounting reason. So when I fixed and stopped unmounting behavior, the problem was solved for me.

For those who want to check mount and unmount behavior for your component, I am happy to provide you example snippet

 // Checking mounting and unmounting if it happens in your component
 useEffect(() => {
            console.log(
                'on mount Element wrapper',
                new Date().getMinutes(), // logs minutes
                new Date().getSeconds() // logs seconds so that you can check diff in browser
            )
            return () => {
                console.log(
                    'on unmount Element wrapper',
                    new Date().getMinutes(), // logs minutes
                    new Date().getSeconds() // logs seconds so that you can check diff in browser
                )
            }
        }, [])

Hi @ all. Came across this error on using setVariables(myParams). As i used refetch(myParams) it solved the problem. Maybe this helps some of you.

v3.4.10 still boots me out of my app and throws the observable error. I downgraded to build 3.2.9, which seems to be the last build before the error was introduced.

I am seeing this when using react-router <Redirect to={someRoute} />. In my case, I run a GQL query to check the status of the user’s account, and in some cases I will redirect the user to a different page. The redirect will happen (url bar updated), but then the error will be thrown at some point during the rendering of the new route.

Using capaj’s workaround fixes the issue for now, but it’s not an idea UX.

Switching the Promise.resolve for a setTimeout(()=>{}, 0) at https://github.com/apollographql/apollo-client/blob/main/src/utilities/observables/Concast.ts#L181 resolves this issue for me. However, I’m not sure if this is a real fix, or that the error is simple suppressed now.

We’re having the same issue on v.3.3.9. Reverting to 3.2.9 is not an option, because it starts throwing Invariant Violations. Any insight on how to fix this? I can confirm that the Apollo Client Provider is being mounted only ONCE and never re-mounted.

Having the same issue here. With me, it also seems to happen when the provider gets unmountend and mounted again.