apollo-client
Version:
A simple yet functional GraphQL client.
146 lines • 7.2 kB
JavaScript
// 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.
var __assign = (this && this.__assign) || Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
import { FetchType } from '../core/types';
import { ObservableQuery } from '../core/ObservableQuery';
import { NetworkStatus } from '../core/networkStatus';
var QueryScheduler = /** @class */ (function () {
function QueryScheduler(_a) {
var queryManager = _a.queryManager, ssrMode = _a.ssrMode;
// Map going from queryIds to query options that are in flight.
this.inFlightQueries = {};
// 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.
this.registeredQueries = {};
// 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.
this.intervalQueries = {};
// Map going from polling interval widths to polling timers.
this.pollingTimers = {};
this.ssrMode = false;
this.queryManager = queryManager;
this.ssrMode = ssrMode || false;
}
QueryScheduler.prototype.checkInFlight = function (queryId) {
var query = this.queryManager.queryStore.get(queryId);
return (query &&
query.networkStatus !== NetworkStatus.ready &&
query.networkStatus !== NetworkStatus.error);
};
QueryScheduler.prototype.fetchQuery = function (queryId, options, fetchType) {
var _this = this;
return new Promise(function (resolve, reject) {
_this.queryManager
.fetchQuery(queryId, options, fetchType)
.then(function (result) {
resolve(result);
})
.catch(function (error) {
reject(error);
});
});
};
QueryScheduler.prototype.startPollingQuery = function (options, queryId, listener) {
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;
};
QueryScheduler.prototype.stopPollingQuery = function (queryId) {
// 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.
QueryScheduler.prototype.fetchQueriesOnInterval = function (interval) {
var _this = this;
// 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(function (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;
}
var queryOptions = _this.registeredQueries[queryId];
var pollingOptions = __assign({}, queryOptions);
pollingOptions.fetchPolicy = 'network-only';
// don't let unhandled rejections happen
_this.fetchQuery(queryId, pollingOptions, FetchType.poll).catch(function () { });
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.
QueryScheduler.prototype.addQueryOnInterval = function (queryId, queryOptions) {
var _this = this;
var 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(function () {
_this.fetchQueriesOnInterval(interval);
}, interval);
}
};
// Used only for unit testing.
QueryScheduler.prototype.registerPollingQuery = function (queryOptions) {
if (!queryOptions.pollInterval) {
throw new Error('Attempted to register a non-polling query with the scheduler.');
}
return new ObservableQuery({
scheduler: this,
options: queryOptions,
});
};
return QueryScheduler;
}());
export { QueryScheduler };
//# sourceMappingURL=scheduler.js.map