New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
potential fix for #10802 #11633
base: main
Are you sure you want to change the base?
potential fix for #10802 #11633
Conversation
🦋 Changeset detectedLatest commit: ec3ade9 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
/release:pr |
size-limit report 📦
|
A new release has been made for this PR. You can install it with |
✅ Deploy Preview for apollo-client-docs ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
Not sure what's up with GitHub right now, but the tests are green on this. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I honestly don't have a better idea on how to fix this. Take a look at my refactoring though and I think you'll see we end up with a bit more readable solution.
Would it also be possible to write a test for this? I think my refactoring is equivalent to the change you made here, but I'm not able to check my work against this.
@@ -252,7 +252,8 @@ export class ObservableQuery< | |||
if ( | |||
// These fetch policies should never deliver data from the cache, unless | |||
// redelivering a previously delivered result. | |||
skipCacheDataFor(fetchPolicy) || | |||
(skipCacheDataFor(fetchPolicy) && | |||
!(fetchPolicy === "network-only" && !this.waitForOwnResult)) || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I always find the !(...)
harder to reason about since you have to understand the stuff inside the parens, then negate it. Instead, we can apply De Morgan's Law which states that "not (A and B)" is equaivalent of "(not A) or (not B)" (essentially distributing the !
to each clause and changing the &&
to an ||
.
I find that a bit easier to reason about:
!(fetchPolicy === "network-only" && !this.waitForOwnResult)) || | |
(fetchPolicy !== "network-only" || this.waitForOwnResult)) || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok now to dissect this further. For me to understand this better, it helps for me to do a bit of refactoring to remove the function calls and inline the checks here. Let's first start with inline method to pull in the body of skipCacheData
in place of its check.
((fetchPolicy === "no-cache" ||
fetchPolicy === "network-only" ||
fetchPolicy === "standby") &&
(fetchPolicy !== "network-only" || this.waitForOwnResult))
This now makes it possible to see the combinations of fetchPolicy
checks here.
Digging further, we know that the 2nd clause won't be executed unless the first is truthy. Using this, we can deduce that fetchPolicy !== 'network-only'
is the same as fetchPolicy === 'no-cache' || fetchPolicy === 'standby'
since these are the only two fetch policies left over from the first check.
((fetchPolicy === "no-cache" ||
fetchPolicy === "network-only" ||
fetchPolicy === "standby") &&
(fetchPolicy === "no-cache" || fetchPolicy === 'standby' || this.waitForOwnResult))
We now begin to really see some repetition here, notably that when fetchPolicy === 'no-cache' || fetchPolicy === 'standby'
is true, the whole thing will always be true.
Let's temporarily refactor out this check to its own variable to make the conditional a bit more obvious.
const absoluteNoCache =
fetchPolicy === "no-cache" || fetchPolicy === "standby";
// ...
((absoluteNoCache || fetchPolicy === "network-only") &&
(absoluteNoCache || this.waitForOwnResult))
This makes it obvious that the condition will pass whenever absoluteNoCache
is true
. We should be able to pull absoluteNoCache
out into its own clause and "or" it with the rest of the logic.
(absoluteNoCache || ((fetchPolicy === "network-only") && (this.waitForOwnResult)))
which is the same as the following when removing unnecessary parens
(absoluteNoCache || (fetchPolicy === "network-only" && this.waitForOwnResult))
Now that we've got the common bits together, let's inline the variable back into the conditional
(fetchPolicy === "no-cache" || fetchPolicy === "standby" || (fetchPolicy === "network-only" && this.waitForOwnResult))
Here is the total of the if
altogether:
if (
// These fetch policies should never deliver data from the cache, unless
// redelivering a previously delivered result.
fetchPolicy === "no-cache" ||
fetchPolicy === "standby" ||
(fetchPolicy === "network-only" && this.waitForOwnResult) ||
// If this.options.query has @client(always: true) fields, we cannot
// trust diff.result, since it was read from the cache without running
// local resolvers (and it's too late to run resolvers now, since we must
// return a result synchronously).
this.queryManager.getDocumentInfo(this.query).hasForcedResolvers
) {
Unfortunately I have no tests to check my work, but hopefully stepping through this makes sense.
I don't have a great recommendation other than the fix you have here. In fact, with the refactor, I'd say its about the same as it was before. The only difference is that you're checking && this.waitForOwnResult
now when using this with a network-only
fetch policy.
Before
// This is the body of skipCacheDataFor(fetchPolicy) inlined
fetchPolicy === "no-cache" ||
fetchPolicy === "standby" ||
fetchPolicy === "network-only"
After
fetchPolicy === "no-cache" ||
fetchPolicy === "standby" ||
(fetchPolicy === "network-only" && this.waitForOwnResult)
Putting it in this light, I feel better about the change 🙂. If you are able to write a test, that would be awesome, then I'd be happy with this PR.
// Unlike network-only or cache-and-network, the no-cache | ||
// FetchPolicy does not switch to cache-first after the first | ||
// network request. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that back in the times of 3.0.0 betas, this was the assumption, which doesn't hold true anymore today (and probably caused this issue).
See #6353
I'll dig on that a bit more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was later moved around in 1e5d4f8 and still is there, but apparently doesn't have the intended effect?
apollo-client/src/core/ObservableQuery.ts
Lines 732 to 742 in ec3ade9
// initial FetchPolicy, they often do not want future cache updates to | |
// trigger unconditional network requests, which is what repeatedly | |
// applying the "cache-and-network" or "network-only" policies would | |
// seem to imply. Instead, when the cache reports an update after the | |
// initial network request, it may be desirable for subsequent network | |
// requests to be triggered only if the cache result is incomplete. To | |
// that end, the options.nextFetchPolicy option provides an easy way to | |
// update options.fetchPolicy after the initial network request, without | |
// having to call observableQuery.setOptions. | |
options.fetchPolicy = options.nextFetchPolicy(fetchPolicy, { | |
reason, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm.. the comment had at that moment in time already been disconnected from code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, this has been removed in 1cd1df9 - but the comment survived until today the comment was adjusted, I had just not re-read the whole black after reading the initial version - I just saw the wall of text with the unchanged first line and assumed it had not changed 😅.
This leaves us with a decision to make: From what I have unearthed, the current behavior of It also matches our documentation for
Note how this says that it will update the cache, but it doesn't say that the cache updates it. => The originally intended way here was that the user has to specify a We'll have to discuss if we want to keep it that way of introduce what is essentially yet another kinda-breaking change to this functionality. |
This is a potential fix for #10802. I'm not exactly happy with this change yet, but it does solve the problem, so I already wanted to create a PR release.