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);