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
76 changes: 28 additions & 48 deletions src/core/ObservableQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ export type ApolloCurrentQueryResult<T> = {
loading: boolean;
networkStatus: NetworkStatus;
error?: ApolloError;
partial?: boolean;
stale?: boolean;
};

Expand Down Expand Up @@ -137,46 +136,43 @@ export class ObservableQuery<
});
}

/**
* Return the result of the query from the local cache as well as some fetching status
* `loading` and `networkStatus` allow to know if a request is in flight
* `partial` lets you know if the result from the local cache is complete or partial
* @return {data: Object, error: ApolloError, loading: boolean, networkStatus: number, partial: boolean}
*/
public getCurrentResult(): ApolloCurrentQueryResult<TData> {
if (this.isTornDown) {
const { lastResult } = this;
return {
data: !this.lastError && lastResult && lastResult.data || void 0,
error: this.lastError,
loading: false,
networkStatus: NetworkStatus.error,
};
}

const { data, partial } = this.queryManager.getCurrentQueryResult(this);
const queryStoreValue = this.queryManager.queryStore.get(this.queryId);
let result: ApolloQueryResult<TData>;

const { lastResult, lastError } = this;
const { fetchPolicy } = this.options;

const isNetworkFetchPolicy =
fetchPolicy === 'network-only' ||
fetchPolicy === 'no-cache';

const networkStatus =
lastError ? NetworkStatus.error :
lastResult ? lastResult.networkStatus :
isNetworkFetchPolicy ? NetworkStatus.loading :
NetworkStatus.ready;

const result: ApolloCurrentQueryResult<TData> = {
data: !lastError && lastResult && lastResult.data || void 0,
error: this.lastError,
loading: isNetworkRequestInFlight(networkStatus),
networkStatus,
};

if (this.isTornDown) {
return result;
}

const queryStoreValue = this.queryManager.queryStore.get(this.queryId);
if (queryStoreValue) {
const { networkStatus } = queryStoreValue;

if (hasError(queryStoreValue, this.options.errorPolicy)) {
return {
return Object.assign(result, {
data: void 0,
loading: false,
networkStatus,
error: new ApolloError({
graphQLErrors: queryStoreValue.graphQLErrors,
networkError: queryStoreValue.networkError,
}),
};
});
}

// Variables might have been added dynamically at query time, when
Expand All @@ -192,38 +188,19 @@ export class ObservableQuery<
this.variables = this.options.variables;
}

result = {
data,
Object.assign(result, {
loading: isNetworkRequestInFlight(networkStatus),
networkStatus,
} as ApolloQueryResult<TData>;
});

if (queryStoreValue.graphQLErrors && this.options.errorPolicy === 'all') {
result.errors = queryStoreValue.graphQLErrors;
}

} else {
// We need to be careful about the loading state we show to the user, to try
// and be vaguely in line with what the user would have seen from .subscribe()
// but to still provide useful information synchronously when the query
// will not end up hitting the server.
// See more: https://github.com/apollostack/apollo-client/issues/707
// Basically: is there a query in flight right now (modolo the next tick)?
const loading = isNetworkFetchPolicy ||
(partial && fetchPolicy !== 'cache-only');

result = {
data,
loading,
networkStatus: loading ? NetworkStatus.loading : NetworkStatus.ready,
} as ApolloQueryResult<TData>;
}

if (!partial) {
this.updateLastResult({ ...result, stale: false });
}
this.updateLastResult({ ...result, stale: false });

return { ...result, partial };
return result;
}

// Compares newResult to the snapshot we took of this.lastResult when it was
Expand Down Expand Up @@ -545,6 +522,9 @@ export class ObservableQuery<
this.lastResultSnapshot = this.queryManager.assumeImmutableResults
? newResult
: cloneDeep(newResult);
if (!isNonEmptyArray(newResult.errors)) {
delete this.lastError;
}
return previousResult;
}

Expand Down
21 changes: 17 additions & 4 deletions src/core/QueryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,9 +431,9 @@ export class QueryManager<TStore> {
fetchMoreForQueryId,
});

this.broadcastQueries();

if (shouldFetch) {
this.broadcastQueries();

const networkResult = this.fetchRequest<T>({
requestId,
queryId,
Expand Down Expand Up @@ -765,11 +765,24 @@ export class QueryManager<TStore> {

let transformedOptions = { ...options } as WatchQueryOptions<TVariables>;

return new ObservableQuery<T, TVariables>({
const observable = new ObservableQuery<T, TVariables>({
queryManager: this,
options: transformedOptions,
shouldSubscribe: shouldSubscribe,
});

this.queryStore.initQuery({
queryId: observable.queryId,
document: this.transform(options.query).document,
variables: options.variables,
storePreviousVariables: false,
isPoll: typeof options.pollInterval === 'number',
isRefetch: false,
metadata: options.metadata,
fetchMoreForQueryId: void 0,
});

return observable;
}

public query<T>(options: QueryOptions): Promise<ApolloQueryResult<T>> {
Expand Down Expand Up @@ -1034,7 +1047,7 @@ export class QueryManager<TStore> {
this.queries.delete(queryId);
}

public getCurrentQueryResult<T>(
private getCurrentQueryResult<T>(
observableQuery: ObservableQuery<T>,
optimistic: boolean = true,
): {
Expand Down
86 changes: 36 additions & 50 deletions src/core/__tests__/ObservableQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,11 +306,8 @@ describe('ObservableQuery', () => {
} else if (handleCount === 3) {
expect(stripSymbols(result.data)).toEqual(data2);
// go back to first set of variables
await observable.setOptions({ variables });
const current = observable.getCurrentResult();
const current = await observable.setOptions({ variables });
expect(stripSymbols(current.data)).toEqual(data);
const secondCurrent = observable.getCurrentResult();
expect(current.data).toEqual(secondCurrent.data);
resolve();
}
});
Expand Down Expand Up @@ -472,11 +469,11 @@ describe('ObservableQuery', () => {
});

subscribeAndCount(reject, observable, async (handleCount, result) => {
if (handleCount === 2) {
if (handleCount === 1) {
expect(stripSymbols(result.data)).toEqual({});
expect(timesFired).toBe(0);
await observable.setOptions({ fetchPolicy: 'cache-first' });
} else if (handleCount === 3) {
} else if (handleCount === 2) {
expect(stripSymbols(result.data)).toEqual(data);
expect(timesFired).toBe(1);
resolve();
Expand Down Expand Up @@ -1452,7 +1449,6 @@ describe('ObservableQuery', () => {
data: dataOne,
loading: false,
networkStatus: 7,
partial: false,
});
resolve();
});
Expand All @@ -1461,7 +1457,6 @@ describe('ObservableQuery', () => {
loading: true,
data: undefined,
networkStatus: 1,
partial: true,
});

setTimeout(
Expand All @@ -1470,39 +1465,12 @@ describe('ObservableQuery', () => {
loading: true,
data: undefined,
networkStatus: 1,
partial: true,
});
}),
0,
);
});

itAsync('returns results from the store immediately', (resolve, reject) => {
const queryManager = mockQueryManager(reject, {
request: { query, variables },
result: { data: dataOne },
});

return queryManager.query({ query, variables }).then((result: any) => {
expect(stripSymbols(result)).toEqual({
data: dataOne,
loading: false,
networkStatus: 7,
stale: false,
});
const observable = queryManager.watchQuery({
query,
variables,
});
expect(stripSymbols(observable.getCurrentResult())).toEqual({
data: dataOne,
loading: false,
networkStatus: 7,
partial: false,
});
}).then(resolve, reject);
});

itAsync('returns errors from the store immediately', (resolve, reject) => {
const queryManager = mockQueryManager(reject, {
request: { query, variables },
Expand Down Expand Up @@ -1592,7 +1560,7 @@ describe('ObservableQuery', () => {
}).then(resolve, reject);
});

itAsync('returns partial data from the store immediately', (resolve, reject) => {
itAsync('returns partial data from the store', (resolve, reject) => {
const superQuery = gql`
query superQuery($id: ID!) {
people_one(id: $id) {
Expand Down Expand Up @@ -1629,10 +1597,9 @@ describe('ObservableQuery', () => {
});

expect(observable.getCurrentResult()).toEqual({
data: dataOne,
data: void 0,
loading: true,
networkStatus: 1,
partial: true,
});

// we can use this to trigger the query
Expand All @@ -1647,19 +1614,28 @@ describe('ObservableQuery', () => {

if (handleCount === 1) {
expect(subResult).toEqual({
data: dataOne,
data: void 0,
loading: true,
networkStatus: 1,
stale: false,
});

} else if (handleCount === 2) {
expect(subResult).toEqual({
data: dataOne,
loading: false,
networkStatus: 7,
stale: false,
});

} else if (handleCount === 3) {
expect(subResult).toEqual({
data: superDataOne,
loading: false,
networkStatus: 7,
stale: false,
});

resolve();
}
});
Expand Down Expand Up @@ -1689,7 +1665,6 @@ describe('ObservableQuery', () => {
data: undefined,
loading: true,
networkStatus: 1,
partial: false,
});

subscribeAndCount(reject, observable, (handleCount, subResult) => {
Expand All @@ -1698,20 +1673,30 @@ describe('ObservableQuery', () => {
loading,
networkStatus,
} = observable.getCurrentResult();

expect(subResult).toEqual({
data,
loading,
networkStatus,
stale: false,
});

if (handleCount === 2) {
if (handleCount === 1) {
expect(stripSymbols(subResult)).toEqual({
data: void 0,
loading: true,
networkStatus: NetworkStatus.loading,
stale: false,
});

} else if (handleCount === 2) {
expect(stripSymbols(subResult)).toEqual({
data: dataTwo,
loading: false,
networkStatus: 7,
networkStatus: NetworkStatus.ready,
stale: false,
});

resolve();
}
});
Expand All @@ -1737,11 +1722,11 @@ describe('ObservableQuery', () => {
variables,
fetchPolicy: 'no-cache',
});

expect(stripSymbols(observable.getCurrentResult())).toEqual({
data: undefined,
loading: true,
networkStatus: 1,
partial: false,
});

subscribeAndCount(reject, observable, (handleCount, subResult) => {
Expand All @@ -1750,14 +1735,15 @@ describe('ObservableQuery', () => {
loading,
networkStatus,
} = observable.getCurrentResult();
expect(subResult).toEqual({
data,
loading,
networkStatus,
stale: false,
});

if (handleCount === 2) {
if (handleCount === 1) {
expect(subResult).toEqual({
data,
loading,
networkStatus,
stale: false,
});
} else if (handleCount === 2) {
expect(stripSymbols(subResult)).toEqual({
data: dataTwo,
loading: false,
Expand Down
Loading