diff --git a/docs/source/api/apollo-client.md b/docs/source/api/apollo-client.md index caa380c7228..c52e1e2265e 100644 --- a/docs/source/api/apollo-client.md +++ b/docs/source/api/apollo-client.md @@ -38,7 +38,7 @@ These options will be merged with options supplied with each request. > **Note:** The React Apollo `` component uses Apollo Client's `watchQuery` functionality, so if you would like to set `defaultOptions` when using ``, 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 %} @@ -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 %}

ObservableQuery

diff --git a/package.json b/package.json index c2e8026fb76..5b1ee679846 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/packages/apollo-client/src/ApolloClient.ts b/packages/apollo-client/src/ApolloClient.ts index 3a270aab636..7021b4c5b70 100644 --- a/packages/apollo-client/src/ApolloClient.ts +++ b/packages/apollo-client/src/ApolloClient.ts @@ -219,6 +219,17 @@ export default class ApolloClient 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 diff --git a/packages/apollo-client/src/__tests__/ApolloClient.ts b/packages/apollo-client/src/__tests__/ApolloClient.ts index 64bcaee9dcd..167a30b9dd6 100644 --- a/packages/apollo-client/src/__tests__/ApolloClient.ts +++ b/packages/apollo-client/src/__tests__/ApolloClient.ts @@ -2190,6 +2190,8 @@ describe('ApolloClient', () => { expect(queryOptions.fetchPolicy).toEqual( defaultOptions.query!.fetchPolicy, ); + + client.stop(); }); }); diff --git a/packages/apollo-client/src/core/QueryManager.ts b/packages/apollo-client/src/core/QueryManager.ts index f8b6ddf11c1..e45e93ec382 100644 --- a/packages/apollo-client/src/core/QueryManager.ts +++ b/packages/apollo-client/src/core/QueryManager.ts @@ -107,6 +107,17 @@ export class QueryManager { 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({ mutation, variables, diff --git a/packages/apollo-client/src/scheduler/__tests__/scheduler.ts b/packages/apollo-client/src/scheduler/__tests__/scheduler.ts index 03f5b6de19b..c009d8d9ef3 100644 --- a/packages/apollo-client/src/scheduler/__tests__/scheduler.ts +++ b/packages/apollo-client/src/scheduler/__tests__/scheduler.ts @@ -76,7 +76,7 @@ describe('QueryScheduler', () => { }); setTimeout(() => { expect(timesFired).toBeGreaterThanOrEqual(0); - scheduler.stopPollingQuery(queryId); + queryManager.stop(); done(); }, 120); }); @@ -114,7 +114,7 @@ describe('QueryScheduler', () => { queryManager, }); let timesFired = 0; - let queryId = scheduler.startPollingQuery( + const queryId = scheduler.startPollingQuery( queryOptions, 'fake-id', queryStoreValue => { @@ -127,6 +127,7 @@ describe('QueryScheduler', () => { setTimeout(() => { expect(timesFired).toEqual(1); + queryManager.stop(); done(); }, 170); }); @@ -174,6 +175,7 @@ describe('QueryScheduler', () => { setTimeout(() => { expect(timesFired).toEqual(1); + queryManager.stop(); done(); }, 100); }); @@ -229,6 +231,7 @@ describe('QueryScheduler', () => { // timesFired end up greater than 2. expect(timesFired).toEqual(2); subscription.unsubscribe(); + queryManager.stop(); done(); }, 100); }); @@ -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.'), ); @@ -271,6 +275,7 @@ describe('QueryScheduler', () => { const queryId = scheduler.intervalQueries[queryOptions.pollInterval][0]; expect(scheduler.checkInFlight(queryId)).toBe(false); subscription.unsubscribe(); + queryManager.stop(); done(); }, }); @@ -305,6 +310,7 @@ describe('QueryScheduler', () => { const subscription = observer.subscribe({}); setTimeout(() => { subscription.unsubscribe(); + queryManager.stop(); done(); }, 100); }); @@ -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', () => { @@ -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 => { @@ -459,6 +468,7 @@ describe('QueryScheduler', () => { setTimeout(() => { expect(timesFired).toEqual(1); + queryManager.stop(); done(); }, 100); }); @@ -504,7 +514,7 @@ describe('QueryScheduler', () => { scheduler.stopPollingQuery(queryId); }); setTimeout(() => { - let queryId2 = scheduler.startPollingQuery( + scheduler.startPollingQuery( queryOptions, 'fake-id2', () => { @@ -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); diff --git a/packages/apollo-client/src/scheduler/scheduler.ts b/packages/apollo-client/src/scheduler/scheduler.ts index 9a320b1dd02..2c19f5eb7db 100644 --- a/packages/apollo-client/src/scheduler/scheduler.ts +++ b/packages/apollo-client/src/scheduler/scheduler.ts @@ -50,6 +50,21 @@ export class QueryScheduler { 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);