Skip to content

Commit

Permalink
Merge pull request #4336 from apollographql/support-ApolloClient-stop…
Browse files Browse the repository at this point in the history
…-method

Support ApolloClient#stop for safe client disposal.
  • Loading branch information
benjamn authored Jan 18, 2019
2 parents 7916775 + edc2828 commit 9d03151
Show file tree
Hide file tree
Showing 7 changed files with 56 additions and 6 deletions.
3 changes: 2 additions & 1 deletion docs/source/api/apollo-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ These options will be merged with options supplied with each request.

> **Note:** The React Apollo `<Query />` component uses Apollo Client's `watchQuery` functionality, so if you would like to set `defaultOptions` when using `<Query />`, be sure to set them under the `defaultOptions.watchQuery` property.
The `ApolloClient` class is the core API for Apollo, and the one you'll need to use no matter which integration you are using:
The `ApolloClient` class is the core API for Apollo, and the one you'll need to use no matter which integration you are using:

{% tsapibox ApolloClient.constructor %}
{% tsapibox ApolloClient.watchQuery %}
Expand All @@ -53,6 +53,7 @@ The `ApolloClient` class is the core API for Apollo, and the one you'll need to
{% tsapibox ApolloClient.onResetStore %}
{% tsapibox ApolloClient.clearStore %}
{% tsapibox ApolloClient.onClearStore %}
{% tsapibox ApolloClient.stop %}

<h2 id="ObservableQuery">ObservableQuery</h2>

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
{
"name": "apollo-client",
"path": "./packages/apollo-client/lib/bundle.min.js",
"maxSize": "9.3 kB"
"maxSize": "9.4 kB"
},
{
"name": "apollo-utilities",
Expand Down
11 changes: 11 additions & 0 deletions packages/apollo-client/src/ApolloClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,17 @@ export default class ApolloClient<TCacheShape> implements DataProxy {
this.clientAwareness.version = clientAwarenessVersion;
}
}

/**
* Call this method to terminate any active client processes, making it safe
* to dispose of this `ApolloClient` instance.
*/
public stop() {
if (this.queryManager) {
this.queryManager.stop();
}
}

/**
* This watches the cache store of the query according to the options specified and
* returns an {@link ObservableQuery}. We can subscribe to this {@link ObservableQuery} and
Expand Down
2 changes: 2 additions & 0 deletions packages/apollo-client/src/__tests__/ApolloClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2190,6 +2190,8 @@ describe('ApolloClient', () => {
expect(queryOptions.fetchPolicy).toEqual(
defaultOptions.query!.fetchPolicy,
);

client.stop();
});
});

Expand Down
11 changes: 11 additions & 0 deletions packages/apollo-client/src/core/QueryManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,17 @@ export class QueryManager<TStore> {
this.scheduler = new QueryScheduler({ queryManager: this, ssrMode });
}

/**
* Call this method to terminate any active query processes, making it safe
* to dispose of this QueryManager instance.
*/
public stop() {
this.scheduler.stop();
this.fetchQueryRejectFns.forEach(reject => {
reject(new Error('QueryManager stopped while query was in flight'));
});
}

public mutate<T>({
mutation,
variables,
Expand Down
18 changes: 14 additions & 4 deletions packages/apollo-client/src/scheduler/__tests__/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('QueryScheduler', () => {
});
setTimeout(() => {
expect(timesFired).toBeGreaterThanOrEqual(0);
scheduler.stopPollingQuery(queryId);
queryManager.stop();
done();
}, 120);
});
Expand Down Expand Up @@ -114,7 +114,7 @@ describe('QueryScheduler', () => {
queryManager,
});
let timesFired = 0;
let queryId = scheduler.startPollingQuery(
const queryId = scheduler.startPollingQuery(
queryOptions,
'fake-id',
queryStoreValue => {
Expand All @@ -127,6 +127,7 @@ describe('QueryScheduler', () => {

setTimeout(() => {
expect(timesFired).toEqual(1);
queryManager.stop();
done();
}, 170);
});
Expand Down Expand Up @@ -174,6 +175,7 @@ describe('QueryScheduler', () => {

setTimeout(() => {
expect(timesFired).toEqual(1);
queryManager.stop();
done();
}, 100);
});
Expand Down Expand Up @@ -229,6 +231,7 @@ describe('QueryScheduler', () => {
// timesFired end up greater than 2.
expect(timesFired).toEqual(2);
subscription.unsubscribe();
queryManager.stop();
done();
}, 100);
});
Expand Down Expand Up @@ -261,6 +264,7 @@ describe('QueryScheduler', () => {
let observableQuery = scheduler.registerPollingQuery(queryOptions);
const subscription = observableQuery.subscribe({
next() {
queryManager.stop();
done.fail(
new Error('Observer provided a result despite a network error.'),
);
Expand All @@ -271,6 +275,7 @@ describe('QueryScheduler', () => {
const queryId = scheduler.intervalQueries[queryOptions.pollInterval][0];
expect(scheduler.checkInFlight(queryId)).toBe(false);
subscription.unsubscribe();
queryManager.stop();
done();
},
});
Expand Down Expand Up @@ -305,6 +310,7 @@ describe('QueryScheduler', () => {
const subscription = observer.subscribe({});
setTimeout(() => {
subscription.unsubscribe();
queryManager.stop();
done();
}, 100);
});
Expand Down Expand Up @@ -344,6 +350,7 @@ describe('QueryScheduler', () => {
];
expect(queries.length).toEqual(1);
expect(queries[0]).toEqual(queryId);
queryManager.stop();
});

it('should add multiple queries to an interval correctly', () => {
Expand Down Expand Up @@ -416,6 +423,8 @@ describe('QueryScheduler', () => {
expect(queryIds.length).toEqual(2);
expect(scheduler.registeredQueries[queryIds[0]]).toEqual(queryOptions1);
expect(scheduler.registeredQueries[queryIds[1]]).toEqual(queryOptions2);

queryManager.stop();
});

it('should remove queries from the interval list correctly', done => {
Expand Down Expand Up @@ -459,6 +468,7 @@ describe('QueryScheduler', () => {

setTimeout(() => {
expect(timesFired).toEqual(1);
queryManager.stop();
done();
}, 100);
});
Expand Down Expand Up @@ -504,7 +514,7 @@ describe('QueryScheduler', () => {
scheduler.stopPollingQuery(queryId);
});
setTimeout(() => {
let queryId2 = scheduler.startPollingQuery(
scheduler.startPollingQuery(
queryOptions,
'fake-id2',
() => {
Expand All @@ -514,7 +524,7 @@ describe('QueryScheduler', () => {
expect(scheduler.intervalQueries[20].length).toEqual(1);
setTimeout(() => {
expect(timesFired).toBeGreaterThanOrEqual(1);
scheduler.stopPollingQuery(queryId2);
queryManager.stop();
done();
}, 80);
}, 200);
Expand Down
15 changes: 15 additions & 0 deletions packages/apollo-client/src/scheduler/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,21 @@ export class QueryScheduler<TCacheShape> {
this.ssrMode = ssrMode || false;
}

/**
* Call this method to terminate any active scheduler timers, making it safe
* to dispose of this QueryScheduler instance.
*/
public stop() {
Object.keys(this.registeredQueries).forEach(queryId => {
this.stopPollingQuery(queryId);
});
// After calling this.stopPollingQuery for all registered queries, calling
// fetchQueriesOnInterval will remove the corresponding intervals.
Object.keys(this.intervalQueries).forEach(interval => {
this.fetchQueriesOnInterval(+interval);
});
}

public checkInFlight(queryId: string) {
const query = this.queryManager.queryStore.get(queryId);

Expand Down

0 comments on commit 9d03151

Please sign in to comment.