Skip to content
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

Avoid reading from cache in ObservableQuery#getCurrentResult. #5565

Merged
merged 7 commits into from
Nov 12, 2019

Conversation

benjamn
Copy link
Member

@benjamn benjamn commented Nov 11, 2019

As an exploration of potential improvements after Apollo Client 3.0, I recently attempted to make InMemoryCache reads asynchronous, so that custom field read functions could return a promise, or perhaps an async iterator, which would ultimately allow us to remove our current local state implementation, which would be a significant bundle size savings.

The bad news: I had to update a lot of tests (no surprise there) in ways that felt too drastic for the Apollo Client 3.x release line, so I think we should postpone the idea of async cache reads until at least Apollo Client 4.0. Oh well!

The silver lining: I made a series of changes to prepare for async cache reads that make lots of sense right now. Hence, the contents of this PR.

The most important change is Stop reading from cache in ObservableQuery#getCurrentResult. (01376a3), which makes the getCurrentResult function return whatever result it most recently received, without falling back to reading from the cache. This change was originally intended to enable async cache reads while keeping getCurrentResult synchronous (which is essential for React rendering, even with Suspense), but I believe this change will also prevent a whole class of inconsistencies due to reading from the cache at arbitrary times, rather than trusting Apollo Client to deliver results from the cache at appropriate times.

When fetchQuery decides not to make a network request (!shouldFetch), we
can safely assume broadcastQueries will be called later in the fetchQuery
method, after the cache has been updated. Calling broadcastQueries
unconditionally in the middle of the fetchQuery method (before the
fetchRequest call) guarantees an unnecessary extra broadcast when
shouldFetch is false, which can lead to multiple UI renders.
Since getCurrentResult must return its result synchronously, reading from
the cache in this method prevents us from making cache reads asynchronous
in the future.

This may be a breaking change for code that attempts to read cache data
from an ObservableQuery immediately after calling watchQuery, but that's a
behavior that should never have been enforced by our test suite in the
first place.
Since we are no longer reading from the cache in getCurrentResult, and the
cache was the source of the partial boolean, and all tested occurrences of
the partial boolean were false anyway, we can safely remove the partial
field from ApolloCurrentQueryResult, which is a small step towards not
needing ApolloCurrentQueryResult in addition to ApolloQueryResult.
@benjamn benjamn self-assigned this Nov 11, 2019
@benjamn benjamn added this to the Release 3.0 milestone Nov 11, 2019
@benjamn benjamn changed the title Avoid reading from cache in get current result Avoid reading from cache in ObservableQuery#getCurrentResult. Nov 12, 2019
Copy link
Member

@hwillson hwillson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all looks great @benjamn (and I'm definitely excited about the idea of getting rid of ApolloCurrentQueryResult). LGTM - thanks!

@benjamn benjamn merged commit 9892d35 into release-3.0 Nov 12, 2019
benjamn added a commit that referenced this pull request Nov 12, 2019
@benjamn benjamn deleted the avoid-reading-from-cache-in-getCurrentResult branch November 12, 2019 16:28
@benjamn benjamn mentioned this pull request Nov 12, 2019
31 tasks
benjamn added a commit that referenced this pull request Nov 21, 2019
In commit 6e0135d (part of PR #5565) I
added a this.queryStore.initQuery call to the QueryManager#watchQuery
method, to ensure the queryStore has information about watched queries as
early as possible. However, I mistakenly copied the isPoll option value
from the initQuery call site in fetchQuery, which caused some watched
queries (those that use polling) to have NetworkStatus.poll instead of
NetworkStatus.loading before any network activity had occurred. This
commit fixes that problem by always passing isPoll:false in watchQuery,
regardless of whether options.pollInterval is defined.
hwillson added a commit that referenced this pull request Nov 30, 2019
The changes made in #5565 mean that `QueryData#fetchData` should
no longer directly rely on `ObservableQuery#getCurrentResult` or
`ObservableQuery#result`. This commit adjusts `fetchData` such
that it returns a new Promise that resolves when the
`ObservableQuery` subscription receives new data, re-using the
same `startQuerySubscription` business rules that non-SSR renders
adhere to. This ensures that the `queryPromises` (in
`react/ssr/RenderPromises`) stored for SSR resolve properly after
new data is received.
hwillson added a commit that referenced this pull request Nov 30, 2019
The changes made in #5565 mean that `QueryData#fetchData` should
no longer directly rely on `ObservableQuery#getCurrentResult` or
`ObservableQuery#result`. This commit adjusts `fetchData` such
that it returns a new Promise that resolves when the
`ObservableQuery` subscription receives new data, re-using the
same `startQuerySubscription` business rules that non-SSR renders
adhere to. This ensures that the `queryPromises` (in
`react/ssr/RenderPromises`) stored for SSR resolve properly after
new data is received.
hwillson added a commit to apollographql/react-apollo that referenced this pull request Nov 30, 2019
Since `ObservableQuery#getCurrentResult` no longer reads from the
cache directly, several React Apollo tests need to be updated to
reflect new loading states that can occur if `getCurrentResult`
doesn't yet have any data to return when it's called.

Also, the previous SSR testing pattern of:

```
return getDataFromTree(app).then(() => {
  const markup = ReactDOM.renderToString(app);
  expect(markup).toMatch(/James/);
});
```

will no longer work, as `ReactDOM.renderToString(app)` will
just return the initial loading state of the component
under test. Instead, we can leverage the markup returned
when `getDataFromTree`'s Promise resolves:

return getDataFromTree(app).then(markup => {
  expect(markup).toMatch(/James/);
});

cc @benjamn
hwillson added a commit that referenced this pull request Dec 2, 2019
* Adjust `fetchData` to accommodate #5565

The changes made in #5565 mean that `QueryData#fetchData` should
no longer directly rely on `ObservableQuery#getCurrentResult` or
`ObservableQuery#result`. This commit adjusts `fetchData` such
that it returns a new Promise that resolves when the
`ObservableQuery` subscription receives new data, re-using the
same `startQuerySubscription` business rules that non-SSR renders
adhere to. This ensures that the `queryPromises` (in
`react/ssr/RenderPromises`) stored for SSR resolve properly after
new data is received.

* Remove no longer valid Promise generic
benjamn added a commit that referenced this pull request Jan 14, 2020
This partially reverts two commits from PR #5565, preserving related
improvements that have happened in the meantime:

* Revert "Remove partial field from ApolloCurrentQueryResult type."
  This reverts commit ca2cef6.

* Revert "Stop reading from cache in ObservableQuery#getCurrentResult."
  This reverts commit 01376a3.

One of the motivations behind the original changes was to lay the
foundations for eventually supporting asynchronous (Promise-based) cache
reads, but that form of asynchrony is no longer a goal of Apollo Client
3.0 or any planned future version, so there's not as much benefit to
weakening getCurrentResult in this way.

On the other hand, allowing getCurrentResult to read synchronously from
the cache ensures that custom field read functions can return new results
immediately, which helps with issues like #5782.

Immediate cache reads have also been the behavior of Apollo Client for
much longer than #5565, which makes this change appealing from an
ease-of-upgrading perspective.
benjamn added a commit that referenced this pull request Jan 14, 2020
This partially reverts two commits from PR #5565, preserving related
improvements that have happened in the meantime:

* Revert "Remove partial field from ApolloCurrentQueryResult type."
  This reverts commit ca2cef6.

* Revert "Stop reading from cache in ObservableQuery#getCurrentResult."
  This reverts commit 01376a3.

One of the motivations behind the original changes was to lay the
foundations for eventually supporting asynchronous (Promise-based) cache
reads, but that form of asynchrony is no longer a goal of Apollo Client
3.0 or any planned future version, so there's not as much benefit to
weakening getCurrentResult in this way.

On the other hand, allowing getCurrentResult to read synchronously from
the cache ensures that custom field read functions can return new results
immediately, which helps with issues like #5782.

Immediate cache reads have also been the behavior of Apollo Client for
much longer than #5565, which makes this change appealing from an
ease-of-upgrading perspective.
benjamn added a commit that referenced this pull request Jan 14, 2020
This partially reverts two commits from PR #5565, preserving related
improvements that have happened in the meantime:

* Revert "Remove partial field from ApolloCurrentQueryResult type."
  This reverts commit ca2cef6.

* Revert "Stop reading from cache in ObservableQuery#getCurrentResult."
  This reverts commit 01376a3.

One of the motivations behind the original changes was to lay the
foundations for eventually supporting asynchronous (Promise-based) cache
reads, but that form of asynchrony is no longer a goal of Apollo Client
3.0 or any planned future version, so there's not as much benefit to
weakening getCurrentResult in this way.

On the other hand, allowing getCurrentResult to read synchronously from
the cache ensures that custom field read functions can return new results
immediately, which helps with issues like #5782.

Immediate cache reads have also been the behavior of Apollo Client for
much longer than #5565, which makes this change appealing from an
ease-of-upgrading perspective.
benjamn added a commit that referenced this pull request Jan 14, 2020
This partially reverts two commits from PR #5565, preserving related
improvements that have happened in the meantime:

* Revert "Remove partial field from ApolloCurrentQueryResult type."
  This reverts commit ca2cef6.

* Revert "Stop reading from cache in ObservableQuery#getCurrentResult."
  This reverts commit 01376a3.

One of the motivations behind the original changes was to lay the
foundations for eventually supporting asynchronous (Promise-based) cache
reads, but that form of asynchrony is no longer a goal of Apollo Client
3.0 or any planned future version, so there's not as much benefit to
weakening getCurrentResult in this way.

On the other hand, allowing getCurrentResult to read synchronously from
the cache ensures that custom field read functions can return new results
immediately, which helps with issues like #5782.

Immediate cache reads have also been the behavior of Apollo Client for
much longer than #5565, which makes this change appealing from an
ease-of-upgrading perspective.
benjamn added a commit that referenced this pull request Jan 14, 2020
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 16, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants