diff --git a/packages/apollo-client/src/__mocks__/mockLinks.ts b/packages/apollo-client/src/__mocks__/mockLinks.ts index ac6e917d19a..3dd4dbd49ac 100644 --- a/packages/apollo-client/src/__mocks__/mockLinks.ts +++ b/packages/apollo-client/src/__mocks__/mockLinks.ts @@ -3,6 +3,7 @@ import { ApolloLink, FetchResult, Observable, + GraphQLRequest, // Observer, } from 'apollo-link'; @@ -25,7 +26,7 @@ export function mockObservableLink(): MockSubscriptionLink { } export interface MockedResponse { - request: Operation; + request: GraphQLRequest; result?: FetchResult; error?: Error; delay?: number; @@ -145,7 +146,7 @@ export class MockSubscriptionLink extends ApolloLink { } } -function requestToKey(request: Operation): string { +function requestToKey(request: GraphQLRequest): string { const queryString = request.query && print(request.query); return JSON.stringify({ diff --git a/packages/apollo-client/src/core/ObservableQuery.ts b/packages/apollo-client/src/core/ObservableQuery.ts index ad43ceee3c4..26782b2cbdf 100644 --- a/packages/apollo-client/src/core/ObservableQuery.ts +++ b/packages/apollo-client/src/core/ObservableQuery.ts @@ -2,11 +2,7 @@ import { isEqual, tryFunctionOrLogError, cloneDeep } from 'apollo-utilities'; import { GraphQLError } from 'graphql'; import { NetworkStatus, isNetworkRequestInFlight } from './networkStatus'; import { Observable, Observer, Subscription } from '../util/Observable'; - -import { QueryScheduler } from '../scheduler/scheduler'; - import { ApolloError } from '../errors/ApolloError'; - import { QueryManager } from './QueryManager'; import { ApolloQueryResult, FetchType, OperationVariables } from './types'; import { @@ -68,10 +64,8 @@ export class ObservableQuery< */ public variables: TVariables; - private isCurrentlyPolling: boolean; private shouldSubscribe: boolean; private isTornDown: boolean; - private scheduler: QueryScheduler; private queryManager: QueryManager; private observers: Observer>[]; private subscriptionHandles: Subscription[]; @@ -81,11 +75,11 @@ export class ObservableQuery< private lastError: ApolloError; constructor({ - scheduler, + queryManager, options, shouldSubscribe = true, }: { - scheduler: QueryScheduler; + queryManager: QueryManager; options: WatchQueryOptions; shouldSubscribe?: boolean; }) { @@ -94,18 +88,16 @@ export class ObservableQuery< ); // active state - this.isCurrentlyPolling = false; this.isTornDown = false; // query information this.options = options; this.variables = options.variables || ({} as TVariables); - this.queryId = scheduler.queryManager.generateQueryId(); + this.queryId = queryManager.generateQueryId(); this.shouldSubscribe = shouldSubscribe; // related classes - this.scheduler = scheduler; - this.queryManager = scheduler.queryManager; + this.queryManager = queryManager; // interal data stores this.observers = []; @@ -524,11 +516,8 @@ export class ObservableQuery< } public stopPolling() { - if (this.isCurrentlyPolling) { - this.scheduler.stopPollingQuery(this.queryId); - this.options.pollInterval = undefined; - this.isCurrentlyPolling = false; - } + this.queryManager.stopPollingQuery(this.queryId); + this.options.pollInterval = undefined; } public startPolling(pollInterval: number) { @@ -541,13 +530,8 @@ export class ObservableQuery< ); } - if (this.isCurrentlyPolling) { - this.scheduler.stopPollingQuery(this.queryId); - this.isCurrentlyPolling = false; - } this.options.pollInterval = pollInterval; - this.isCurrentlyPolling = true; - this.scheduler.startPollingQuery(this.options, this.queryId); + this.queryManager.startPollingQuery(this.options, this.queryId); } private onSubscribe(observer: Observer>) { @@ -598,8 +582,7 @@ export class ObservableQuery< ); } - this.isCurrentlyPolling = true; - this.scheduler.startPollingQuery(this.options, this.queryId); + this.queryManager.startPollingQuery(this.options, this.queryId); } const observer: Observer> = { @@ -627,11 +610,7 @@ export class ObservableQuery< private tearDownQuery() { this.isTornDown = true; - - if (this.isCurrentlyPolling) { - this.scheduler.stopPollingQuery(this.queryId); - this.isCurrentlyPolling = false; - } + this.queryManager.stopPollingQuery(this.queryId); // stop all active GraphQL subscriptions this.subscriptionHandles.forEach(sub => sub.unsubscribe()); diff --git a/packages/apollo-client/src/core/QueryManager.ts b/packages/apollo-client/src/core/QueryManager.ts index e45e93ec382..61191ab522a 100644 --- a/packages/apollo-client/src/core/QueryManager.ts +++ b/packages/apollo-client/src/core/QueryManager.ts @@ -13,8 +13,6 @@ import { hasDirectives, } from 'apollo-utilities'; -import { QueryScheduler } from '../scheduler/scheduler'; - import { isApolloError, ApolloError } from '../errors/ApolloError'; import { Observer, Subscription, Observable } from '../util/Observable'; @@ -54,7 +52,6 @@ export interface QueryInfo { } export class QueryManager { - public scheduler: QueryScheduler; public link: ApolloLink; public mutationStore: MutationStore = new MutationStore(); public queryStore: QueryStore = new QueryStore(); @@ -66,6 +63,8 @@ export class QueryManager { private onBroadcast: () => void; + private ssrMode: boolean; + // let's not start at zero to avoid pain with bad checks private idCounter = 1; @@ -104,7 +103,7 @@ export class QueryManager { this.dataStore = store; this.onBroadcast = onBroadcast; this.clientAwareness = clientAwareness; - this.scheduler = new QueryScheduler({ queryManager: this, ssrMode }); + this.ssrMode = ssrMode; } /** @@ -112,7 +111,10 @@ export class QueryManager { * to dispose of this QueryManager instance. */ public stop() { - this.scheduler.stop(); + this.pollingInfoByQueryId.forEach((_info, queryId) => { + this.stopPollingQuery(queryId); + }); + this.fetchQueryRejectFns.forEach(reject => { reject(new Error('QueryManager stopped while query was in flight')); }); @@ -673,7 +675,7 @@ export class QueryManager { let transformedOptions = { ...options } as WatchQueryOptions; return new ObservableQuery({ - scheduler: this.scheduler, + queryManager: this, options: transformedOptions, shouldSubscribe: shouldSubscribe, }); @@ -1280,4 +1282,133 @@ export class QueryManager { }, }; } + + public checkInFlight(queryId: string) { + const query = this.queryStore.get(queryId); + + return ( + query && + query.networkStatus !== NetworkStatus.ready && + query.networkStatus !== NetworkStatus.error + ); + } + + // Map from client ID to { interval, options }. + private pollingInfoByQueryId = new Map(); + + private nextPoll: { + time: number; + timeout: NodeJS.Timeout; + } | null = null; + + public startPollingQuery( + options: WatchQueryOptions, + queryId: string, + listener?: QueryListener, + ): string { + const { pollInterval } = options; + + if (!pollInterval) { + throw new Error( + 'Attempted to start a polling query without a polling interval.', + ); + } + + // Do not poll in SSR mode + if (!this.ssrMode) { + this.pollingInfoByQueryId.set(queryId, { + interval: pollInterval, + // Avoid polling until at least pollInterval milliseconds from now. + // The -10 is a fudge factor to help with tests that rely on simulated + // timeouts via jest.runTimersToTime. + lastPollTimeMs: Date.now() - 10, + options: { + ...options, + fetchPolicy: 'network-only', + }, + }); + + if (listener) { + this.addQueryListener(queryId, listener); + } + + this.schedulePoll(pollInterval); + } + + return queryId; + } + + public stopPollingQuery(queryId: string) { + // Since the master polling interval dynamically adjusts to the contents of + // this.pollingInfoByQueryId, stopping a query from polling is as easy as + // removing it from the map. + this.pollingInfoByQueryId.delete(queryId); + } + + // Calling this method ensures a poll will happen within the specified time + // limit, canceling any pending polls that would not happen in time. + private schedulePoll(timeLimitMs: number) { + const now = Date.now(); + + if (this.nextPoll) { + if (timeLimitMs < this.nextPoll.time - now) { + // The next poll will happen too far in the future, so cancel it, and + // fall through to scheduling a new timeout. + clearTimeout(this.nextPoll.timeout); + } else { + // The next poll will happen within timeLimitMs, so all is well. + return; + } + } + + this.nextPoll = { + // Estimated time when the timeout will fire. + time: now + timeLimitMs, + + timeout: setTimeout(() => { + this.nextPoll = null; + let nextTimeLimitMs = Infinity; + + this.pollingInfoByQueryId.forEach((info, queryId) => { + // Pick next timeout according to current minimum interval. + if (info.interval < nextTimeLimitMs) { + nextTimeLimitMs = info.interval; + } + + if (!this.checkInFlight(queryId)) { + // If this query was last polled more than interval milliseconds + // ago, poll it now. Note that there may be a small delay between + // the desired polling time and the actual polling time (equal to + // at most the minimum polling interval across all queries), but + // that's the tradeoff to batching polling intervals. + if (Date.now() - info.lastPollTimeMs >= info.interval) { + const updateLastPollTime = () => { + info.lastPollTimeMs = Date.now(); + }; + this.fetchQuery(queryId, info.options, FetchType.poll).then( + // Set info.lastPollTimeMs after the fetch completes, whether + // or not it succeeded. Promise.prototype.finally would be nice + // here, but we don't have a polyfill for that at the moment, + // and this code has historically silenced errors, which is not + // the behavior of .finally(updateLastPollTime). + updateLastPollTime, + updateLastPollTime + ); + } + } + }); + + // If there were no entries in this.pollingInfoByQueryId, then + // nextTimeLimitMs will still be Infinity, so this.schedulePoll will + // not be called, thus ending the master polling interval. + if (isFinite(nextTimeLimitMs)) { + this.schedulePoll(nextTimeLimitMs); + } + }, timeLimitMs), + }; + } } diff --git a/packages/apollo-client/src/scheduler/__tests__/scheduler.ts b/packages/apollo-client/src/core/__tests__/scheduler.ts similarity index 77% rename from packages/apollo-client/src/scheduler/__tests__/scheduler.ts rename to packages/apollo-client/src/core/__tests__/scheduler.ts index c009d8d9ef3..a4265d0a42e 100644 --- a/packages/apollo-client/src/scheduler/__tests__/scheduler.ts +++ b/packages/apollo-client/src/core/__tests__/scheduler.ts @@ -2,13 +2,38 @@ import { InMemoryCache } from 'apollo-cache-inmemory'; import gql from 'graphql-tag'; import { stripSymbols } from 'apollo-utilities'; -import { QueryScheduler } from '../scheduler'; -import { QueryManager } from '../../core/QueryManager'; +import { QueryManager } from '../QueryManager'; import { WatchQueryOptions } from '../../core/watchQueryOptions'; import { mockSingleLink } from '../../__mocks__/mockLinks'; import { NetworkStatus } from '../../core/networkStatus'; import { DataStore } from '../../data/store'; +import { ObservableQuery } from '../../core/ObservableQuery'; + +// Used only for unit testing. +function registerPollingQuery( + queryManager: QueryManager, + queryOptions: WatchQueryOptions, +): ObservableQuery { + if (!queryOptions.pollInterval) { + throw new Error( + 'Attempted to register a non-polling query with the scheduler.', + ); + } + return new ObservableQuery({ + queryManager, + options: queryOptions, + }); +} + +function eachPollingQuery( + queryManager: QueryManager, + callback: (queryId: string, info: any) => any, +) { + (queryManager as any).pollingInfoByQueryId.forEach( + (info: any, queryId: string) => callback(queryId, info), + ); +} describe('QueryScheduler', () => { it('should throw an error if we try to start polling a non-polling query', () => { @@ -17,10 +42,6 @@ describe('QueryScheduler', () => { store: new DataStore(new InMemoryCache({ addTypename: false })), }); - const scheduler = new QueryScheduler({ - queryManager, - }); - const query = gql` query { author { @@ -33,7 +54,7 @@ describe('QueryScheduler', () => { query, }; expect(() => { - scheduler.startPollingQuery(queryOptions, null as never); + queryManager.startPollingQuery(queryOptions, null as never); }).toThrow(); }); @@ -67,11 +88,8 @@ describe('QueryScheduler', () => { link: link, }); - const scheduler = new QueryScheduler({ - queryManager, - }); let timesFired = 0; - const queryId = scheduler.startPollingQuery(queryOptions, 'fake-id', () => { + queryManager.startPollingQuery(queryOptions, 'fake-id', () => { timesFired += 1; }); setTimeout(() => { @@ -110,17 +128,14 @@ describe('QueryScheduler', () => { store: new DataStore(new InMemoryCache({ addTypename: false })), link: link, }); - const scheduler = new QueryScheduler({ - queryManager, - }); let timesFired = 0; - const queryId = scheduler.startPollingQuery( + const queryId = queryManager.startPollingQuery( queryOptions, 'fake-id', queryStoreValue => { if (queryStoreValue.networkStatus !== NetworkStatus.poll) { timesFired += 1; - scheduler.stopPollingQuery(queryId); + queryManager.stopPollingQuery(queryId); } }, ); @@ -160,11 +175,8 @@ describe('QueryScheduler', () => { link, }); - const scheduler = new QueryScheduler({ - queryManager, - }); let timesFired = 0; - let observableQuery = scheduler.registerPollingQuery(queryOptions); + let observableQuery = registerPollingQuery(queryManager, queryOptions); let subscription = observableQuery.subscribe({ next(result) { timesFired += 1; @@ -208,11 +220,8 @@ describe('QueryScheduler', () => { store: new DataStore(new InMemoryCache({ addTypename: false })), link, }); - const scheduler = new QueryScheduler({ - queryManager, - }); let timesFired = 0; - let observableQuery = scheduler.registerPollingQuery(queryOptions); + let observableQuery = registerPollingQuery(queryManager, queryOptions); let subscription = observableQuery.subscribe({ next(result) { expect(stripSymbols(result.data)).toEqual(data[timesFired]); @@ -258,10 +267,7 @@ describe('QueryScheduler', () => { store: new DataStore(new InMemoryCache({ addTypename: false })), link, }); - const scheduler = new QueryScheduler({ - queryManager, - }); - let observableQuery = scheduler.registerPollingQuery(queryOptions); + let observableQuery = registerPollingQuery(queryManager, queryOptions); const subscription = observableQuery.subscribe({ next() { queryManager.stop(); @@ -272,8 +278,9 @@ describe('QueryScheduler', () => { error(errorVal) { expect(errorVal).toBeDefined(); - const queryId = scheduler.intervalQueries[queryOptions.pollInterval][0]; - expect(scheduler.checkInFlight(queryId)).toBe(false); + eachPollingQuery(queryManager, queryId => { + expect(queryManager.checkInFlight(queryId)).toBe(false); + }); subscription.unsubscribe(); queryManager.stop(); done(); @@ -303,10 +310,7 @@ describe('QueryScheduler', () => { store: new DataStore(new InMemoryCache()), link, }); - const scheduler = new QueryScheduler({ - queryManager, - }); - const observer = scheduler.registerPollingQuery(queryOptions); + const observer = registerPollingQuery(queryManager, queryOptions); const subscription = observer.subscribe({}); setTimeout(() => { subscription.unsubscribe(); @@ -332,24 +336,20 @@ describe('QueryScheduler', () => { request: queryOptions, result: { data }, }); - const queryManager = new QueryManager({ + const queryManager = new QueryManager({ store: new DataStore(new InMemoryCache()), link, }); - const scheduler = new QueryScheduler({ - queryManager, - }); const queryId = 'fake-id'; - scheduler.addQueryOnInterval(queryId, queryOptions); - expect(Object.keys(scheduler.intervalQueries).length).toEqual(1); - expect(Object.keys(scheduler.intervalQueries)[0]).toEqual( - queryOptions.pollInterval.toString(), - ); - const queries = (scheduler.intervalQueries)[ - queryOptions.pollInterval.toString() - ]; - expect(queries.length).toEqual(1); - expect(queries[0]).toEqual(queryId); + queryManager.startPollingQuery(queryOptions, queryId); + + let count = 0; + eachPollingQuery(queryManager, (qid, info) => { + ++count; + expect(info.interval).toEqual(queryOptions.pollInterval); + expect(qid).toEqual(queryId); + }); + expect(count).toEqual(1); queryManager.stop(); }); @@ -398,32 +398,26 @@ describe('QueryScheduler', () => { }, ), }); - const scheduler = new QueryScheduler({ - queryManager, - }); - const observable1 = scheduler.registerPollingQuery(queryOptions1); + const observable1 = registerPollingQuery(queryManager, queryOptions1); observable1.subscribe({ next() { //do nothing }, }); - const observable2 = scheduler.registerPollingQuery(queryOptions2); + const observable2 = registerPollingQuery(queryManager, queryOptions2); observable2.subscribe({ next() { //do nothing }, }); - const keys = Object.keys(scheduler.intervalQueries); - expect(keys.length).toEqual(1); - expect(keys[0]).toEqual(String(interval)); - - const queryIds = (scheduler.intervalQueries)[keys[0]]; - expect(queryIds.length).toEqual(2); - expect(scheduler.registeredQueries[queryIds[0]]).toEqual(queryOptions1); - expect(scheduler.registeredQueries[queryIds[1]]).toEqual(queryOptions2); - + let count = 0; + eachPollingQuery(queryManager, (_, info) => { + expect(info.interval).toEqual(interval); + ++count; + }); + expect(count).toEqual(2); queryManager.stop(); }); @@ -449,11 +443,8 @@ describe('QueryScheduler', () => { result: { data }, }), }); - const scheduler = new QueryScheduler({ - queryManager, - }); let timesFired = 0; - const observable = scheduler.registerPollingQuery({ + const observable = registerPollingQuery(queryManager, { query, pollInterval: 10, }); @@ -462,7 +453,10 @@ describe('QueryScheduler', () => { timesFired += 1; expect(stripSymbols(result.data)).toEqual(data); subscription.unsubscribe(); - expect(Object.keys(scheduler.registeredQueries).length).toEqual(0); + + let pollingCount = 0; + eachPollingQuery(queryManager, () => ++pollingCount); + expect(pollingCount).toEqual(0); }, }); @@ -506,22 +500,18 @@ describe('QueryScheduler', () => { store: new DataStore(new InMemoryCache({ addTypename: false })), link: link, }); - const scheduler = new QueryScheduler({ - queryManager, - }); let timesFired = 0; - let queryId = scheduler.startPollingQuery(queryOptions, 'fake-id', () => { - scheduler.stopPollingQuery(queryId); - }); + let queryId = queryManager.startPollingQuery( + queryOptions, + 'fake-id', + () => { + queryManager.stopPollingQuery(queryId); + }, + ); setTimeout(() => { - scheduler.startPollingQuery( - queryOptions, - 'fake-id2', - () => { - timesFired += 1; - }, - ); - expect(scheduler.intervalQueries[20].length).toEqual(1); + queryManager.startPollingQuery(queryOptions, 'fake-id2', () => { + timesFired += 1; + }); setTimeout(() => { expect(timesFired).toBeGreaterThanOrEqual(1); queryManager.stop(); diff --git a/packages/apollo-client/src/scheduler/scheduler.ts b/packages/apollo-client/src/scheduler/scheduler.ts deleted file mode 100644 index 2c19f5eb7db..00000000000 --- a/packages/apollo-client/src/scheduler/scheduler.ts +++ /dev/null @@ -1,218 +0,0 @@ -// The QueryScheduler is supposed to be a mechanism that schedules polling queries such that -// they are clustered into the time slots of the QueryBatcher and are batched together. It -// also makes sure that for a given polling query, if one instance of the query is inflight, -// another instance will not be fired until the query returns or times out. We do this because -// another query fires while one is already in flight, the data will stay in the "loading" state -// even after the first query has returned. - -// At the moment, the QueryScheduler implements the one-polling-instance-at-a-time logic and -// adds queries to the QueryBatcher queue. - -import { QueryManager } from '../core/QueryManager'; - -import { FetchType, QueryListener } from '../core/types'; - -import { ObservableQuery } from '../core/ObservableQuery'; - -import { WatchQueryOptions } from '../core/watchQueryOptions'; - -import { NetworkStatus } from '../core/networkStatus'; - -export class QueryScheduler { - // Map going from queryIds to query options that are in flight. - public inFlightQueries: { [queryId: string]: WatchQueryOptions } = {}; - - // Map going from query ids to the query options associated with those queries. Contains all of - // the queries, both in flight and not in flight. - public registeredQueries: { [queryId: string]: WatchQueryOptions } = {}; - - // Map going from polling interval with to the query ids that fire on that interval. - // These query ids are associated with a set of options in the this.registeredQueries. - public intervalQueries: { [interval: number]: string[] } = {}; - - // We use this instance to actually fire queries (i.e. send them to the batching - // mechanism). - public queryManager: QueryManager; - - // Map going from polling interval widths to polling timers. - private pollingTimers: { [interval: number]: any } = {}; - - private ssrMode: boolean = false; - - constructor({ - queryManager, - ssrMode, - }: { - queryManager: QueryManager; - ssrMode?: boolean; - }) { - this.queryManager = queryManager; - 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); - - return ( - query && - query.networkStatus !== NetworkStatus.ready && - query.networkStatus !== NetworkStatus.error - ); - } - - public fetchQuery( - queryId: string, - options: WatchQueryOptions, - fetchType: FetchType, - ) { - return new Promise((resolve, reject) => { - this.queryManager - .fetchQuery(queryId, options, fetchType) - .then(result => { - resolve(result); - }) - .catch(error => { - reject(error); - }); - }); - } - - public startPollingQuery( - options: WatchQueryOptions, - queryId: string, - listener?: QueryListener, - ): string { - if (!options.pollInterval) { - throw new Error( - 'Attempted to start a polling query without a polling interval.', - ); - } - - // Do not poll in SSR mode - if (this.ssrMode) return queryId; - - this.registeredQueries[queryId] = options; - - if (listener) { - this.queryManager.addQueryListener(queryId, listener); - } - this.addQueryOnInterval(queryId, options); - - return queryId; - } - - public stopPollingQuery(queryId: string) { - // Remove the query options from one of the registered queries. - // The polling function will then take care of not firing it anymore. - delete this.registeredQueries[queryId]; - } - - // Fires the all of the queries on a particular interval. Called on a setInterval. - public fetchQueriesOnInterval(interval: number) { - // XXX this "filter" here is nasty, because it does two things at the same time. - // 1. remove queries that have stopped polling - // 2. call fetchQueries for queries that are polling and not in flight. - // TODO: refactor this to make it cleaner - this.intervalQueries[interval] = this.intervalQueries[interval].filter( - queryId => { - // If queryOptions can't be found from registeredQueries or if it has a - // different interval, it means that this queryId is no longer registered - // and should be removed from the list of queries firing on this interval. - // - // We don't remove queries from intervalQueries immediately in - // stopPollingQuery so that we can keep the timer consistent when queries - // are removed and replaced, and to avoid quadratic behavior when stopping - // many queries. - if ( - !( - this.registeredQueries.hasOwnProperty(queryId) && - this.registeredQueries[queryId].pollInterval === interval - ) - ) { - return false; - } - - // Don't fire this instance of the polling query is one of the instances is already in - // flight. - if (this.checkInFlight(queryId)) { - return true; - } - - const queryOptions = this.registeredQueries[queryId]; - const pollingOptions = { ...queryOptions } as WatchQueryOptions; - pollingOptions.fetchPolicy = 'network-only'; - // don't let unhandled rejections happen - this.fetchQuery(queryId, pollingOptions, FetchType.poll).catch( - () => {}, - ); - return true; - }, - ); - - if (this.intervalQueries[interval].length === 0) { - clearInterval(this.pollingTimers[interval]); - delete this.intervalQueries[interval]; - } - } - - // Adds a query on a particular interval to this.intervalQueries and then fires - // that query with all the other queries executing on that interval. Note that the query id - // and query options must have been added to this.registeredQueries before this function is called. - public addQueryOnInterval( - queryId: string, - queryOptions: WatchQueryOptions, - ) { - const interval = queryOptions.pollInterval; - - if (!interval) { - throw new Error( - `A poll interval is required to start polling query with id '${queryId}'.`, - ); - } - - // If there are other queries on this interval, this query will just fire with those - // and we don't need to create a new timer. - if ( - this.intervalQueries.hasOwnProperty(interval.toString()) && - this.intervalQueries[interval].length > 0 - ) { - this.intervalQueries[interval].push(queryId); - } else { - this.intervalQueries[interval] = [queryId]; - // set up the timer for the function that will handle this interval - this.pollingTimers[interval] = setInterval(() => { - this.fetchQueriesOnInterval(interval); - }, interval); - } - } - - // Used only for unit testing. - public registerPollingQuery( - queryOptions: WatchQueryOptions, - ): ObservableQuery { - if (!queryOptions.pollInterval) { - throw new Error( - 'Attempted to register a non-polling query with the scheduler.', - ); - } - return new ObservableQuery({ - scheduler: this, - options: queryOptions, - }); - } -} diff --git a/packages/apollo-client/tsconfig.test.json b/packages/apollo-client/tsconfig.test.json index ebc64752590..1c4af829781 100644 --- a/packages/apollo-client/tsconfig.test.json +++ b/packages/apollo-client/tsconfig.test.json @@ -19,6 +19,5 @@ "src/core/__tests__/fetchPolicies.ts", "src/data/__tests__/queries.ts", "src/errors/__tests__/ApolloError.ts", - "src/scheduler/__tests__/scheduler.ts" ] }