apollographql / apollo-client

:rocket:  A fully-featured, production ready caching GraphQL client for every UI framework and GraphQL server.
https://apollographql.com/client
MIT License
19.38k stars 2.66k forks source link

Custom ApolloLink using `next.subscribe` is making duplicated requests #11871

Closed jca41 closed 5 months ago

jca41 commented 5 months ago

Issue Description

Given the example custom link

export class MyLink extends ApolloLink {
  constructor() {
    super((operation, forward) => {
      const next: Observable<FetchResult> = forward(operation);

      next.subscribe({
        error: (e) => {
          console.log(e);
        },
        next: (result) => {
          console.log(result);
        },
      });

      return next;
    });
  }
}

By using next.subscribe i'm seeing duplicated queries/mutations.

This happened after bumping from 3.7.14 to 3.10.4.

Any help debugging this issue would be appreciated!

Link to Reproduction

https://github.com/jca41/link-subscribe-duplicated-requests

Reproduction Steps

No response

@apollo/client version

3.10.4

jca41 commented 5 months ago

Updated description with repro link: https://github.com/jca41/link-subscribe-duplicated-requests

jca41 commented 5 months ago

Also would suggest the team to have a bug report template that uses an external API via HTTP.

jerelmiller commented 5 months ago

Hey @jca41 👋

This is actually expected behavior due to how observables work. Anytime you subscribe to it, it will execute that observable's callback function. If you look at HttpLink, you'll see that callback function is where the fetch happens. In the example you've given, you're calling subscribe in the link and returning the same observable back to ApolloClient which also subscribes itself to that observable, hence why you end up with two network requests.

There are a couple different ways you can fix this:

1: Use .map instead of .subscribe which lets you observe the emitted result but returns a new observable back to Apollo Client core. The downside here is that you'll only be able to observe the next values and not the error values. If you need access to errors, try the next technique.

export class MyLink extends ApolloLink {
  constructor() {
    super((operation, forward) => {
      return forward(operation).map((result) => {
        console.log(result);
        return result;
      });
    });
  }
}
  1. Return a new observable that emits results from the forwarded observable. This ensures the observable that Apollo Client subscribes to isn't the same one that creates the network request. This is the technique that several of our built-in links do in order to add additional behavior.
export class MyLink extends ApolloLink {
  constructor() {
    super((operation, forward) => {
      return new Observable((observer) => {
        const subscription = forward(operation).subscribe({
          next: (result) => {
            console.log(result);
            observer.next(result);
          },
          error: (e) => {
            console.log(e);
            observer.error(e);
          },
          // very important that you include this to ensure you let the observer
          // know this operation is completed!
          complete: observer.complete.bind(observer)
        });

        // if the upstream observer stops watching for changes, unsubscribe from the forwarded operation as well
        return () => subscription.unsubscribe();
      })
    });
  }
}

Try either of these and see if this works better for you!

jca41 commented 5 months ago

Ah! I guess something got fixed in later versions. Many thanks for the explanation, I will try your suggestions 😊

jerelmiller commented 5 months ago

Definitely interesting that it deduped before since it probably shouldn't have 😆. Hope the suggestions work for you 🤞

jca41 commented 5 months ago

Thanks @jerelmiller second option seems to be working great! 😊

github-actions[bot] commented 5 months ago

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better.

jerelmiller commented 5 months ago

Glad to hear it!

github-actions[bot] commented 4 months ago

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. For general questions, we recommend using StackOverflow or our discord server.