UNPKG

react-relay

Version:

A framework for building data-driven React applications.

283 lines (227 loc) • 9.45 kB
/** * Copyright 2013-2015, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule RelayPendingQueryTracker * @typechecks * */ 'use strict'; var _classCallCheck = require('babel-runtime/helpers/class-call-check')['default']; var _Object$keys = require('babel-runtime/core-js/object/keys')['default']; var Deferred = require('fbjs/lib/Deferred'); var DliteFetchModeConstants = require('./DliteFetchModeConstants'); var GraphQLDeferredQueryTracker = require('./GraphQLDeferredQueryTracker'); var Promise = require('fbjs/lib/Promise'); var PromiseMap = require('fbjs/lib/PromiseMap'); var RelayTaskScheduler = require('./RelayTaskScheduler'); var containsRelayQueryRootCall = require('./containsRelayQueryRootCall'); var everyObject = require('fbjs/lib/everyObject'); var fetchRelayQuery = require('./fetchRelayQuery'); var invariant = require('fbjs/lib/invariant'); var subtractRelayQuery = require('./subtractRelayQuery'); var pendingFetchMap = {}; // Asynchronous mapping from preload query IDs to results. var preloadQueryMap = new PromiseMap(); var PendingFetch = (function () { function PendingFetch(_ref) { var fetchMode = _ref.fetchMode; var forceIndex = _ref.forceIndex; var query = _ref.query; var storeData = _ref.storeData; _classCallCheck(this, PendingFetch); var queryID = query.getID(); this._storeData = storeData; this._query = query; this._forceIndex = forceIndex; this._resolvedSubtractedQuery = false; this._resolvedDeferred = new Deferred(); this._dependents = []; this._pendingDependencyMap = {}; var subtractedQuery; if (fetchMode === DliteFetchModeConstants.FETCH_MODE_PRELOAD) { subtractedQuery = query; this._fetchSubtractedQueryPromise = preloadQueryMap.get(queryID); } else { subtractedQuery = this._subtractPending(query); this._fetchSubtractedQueryPromise = subtractedQuery ? fetchRelayQuery(subtractedQuery) : Promise.resolve(); } this._fetchedSubtractedQuery = !subtractedQuery; this._errors = []; if (subtractedQuery) { pendingFetchMap[queryID] = { fetch: this, query: subtractedQuery }; GraphQLDeferredQueryTracker.recordQuery(subtractedQuery); this._fetchSubtractedQueryPromise.done(this._handleSubtractedQuerySuccess.bind(this, subtractedQuery), this._handleSubtractedQueryFailure.bind(this, subtractedQuery)); } else { this._markSubtractedQueryAsResolved(); } } /** * Subtracts all pending queries from the supplied `query` and returns the * resulting difference. The difference can be null if the entire query is * pending. * * If any pending queries were subtracted, they will be added as dependencies * and the query will only resolve once the subtracted query and all * dependencies have resolved. * * This, combined with our use of diff queries (see `diffRelayQuery`) means * that we only go to the server for things that are not in (or not on their * way to) the cache (`RelayRecordStore`). */ PendingFetch.prototype._subtractPending = function _subtractPending(query) { var _this = this; everyObject(pendingFetchMap, function (pending) { // Stop if the entire query is subtracted. if (!query) { return false; } if (containsRelayQueryRootCall(pending.query, query)) { var subtractedQuery = subtractRelayQuery(query, pending.query); if (subtractedQuery !== query) { query = subtractedQuery; _this._addPendingDependency(pending.fetch); } } return true; }); return query; }; PendingFetch.prototype._addPendingDependency = function _addPendingDependency(pendingFetch) { var queryID = pendingFetch.getQuery().getID(); this._pendingDependencyMap[queryID] = pendingFetch; pendingFetch._addDependent(this); }; PendingFetch.prototype._addDependent = function _addDependent(pendingFetch) { this._dependents.push(pendingFetch); }; PendingFetch.prototype._handleSubtractedQuerySuccess = function _handleSubtractedQuerySuccess(subtractedQuery, result) { var _this2 = this; this._fetchedSubtractedQuery = true; RelayTaskScheduler.await(function () { var response = result.response; !(response && typeof response === 'object') ? process.env.NODE_ENV !== 'production' ? invariant(false, 'RelayPendingQueryTracker: Expected response to be an object, got ' + '`%s`.', response ? typeof response : response) : invariant(false) : undefined; _this2._storeData.handleQueryPayload(subtractedQuery, response, _this2._forceIndex); GraphQLDeferredQueryTracker.resolveQuery(subtractedQuery, response, result.ref_params); }).done(this._markSubtractedQueryAsResolved.bind(this), this._markAsRejected.bind(this)); }; PendingFetch.prototype._handleSubtractedQueryFailure = function _handleSubtractedQueryFailure(subtractedQuery, error) { GraphQLDeferredQueryTracker.rejectQuery(subtractedQuery, error); this._markAsRejected(error); }; PendingFetch.prototype._markSubtractedQueryAsResolved = function _markSubtractedQueryAsResolved() { var queryID = this.getQuery().getID(); delete pendingFetchMap[queryID]; this._resolvedSubtractedQuery = true; this._updateResolvedDeferred(); this._dependents.forEach(function (dependent) { return dependent._markDependencyAsResolved(queryID); }); }; PendingFetch.prototype._markAsRejected = function _markAsRejected(error) { var queryID = this.getQuery().getID(); delete pendingFetchMap[queryID]; console.error(error.message); this._errors.push(error); this._updateResolvedDeferred(); this._dependents.forEach(function (dependent) { return dependent._markDependencyAsRejected(queryID, error); }); }; PendingFetch.prototype._markDependencyAsResolved = function _markDependencyAsResolved(dependencyQueryID) { delete this._pendingDependencyMap[dependencyQueryID]; this._updateResolvedDeferred(); }; PendingFetch.prototype._markDependencyAsRejected = function _markDependencyAsRejected(dependencyQueryID, error) { delete this._pendingDependencyMap[dependencyQueryID]; this._errors.push(error); this._updateResolvedDeferred(); // Dependencies further down the graph are either not affected or informed // by `dependencyQueryID`. }; PendingFetch.prototype._updateResolvedDeferred = function _updateResolvedDeferred() { if (this._isSettled() && !this._resolvedDeferred.isSettled()) { if (this._errors.length) { this._resolvedDeferred.reject(this._errors[0]); } else { this._resolvedDeferred.resolve(undefined); } } }; PendingFetch.prototype._isSettled = function _isSettled() { return this._errors.length > 0 || this._resolvedSubtractedQuery && !hasItems(this._pendingDependencyMap); }; PendingFetch.prototype.getQuery = function getQuery() { return this._query; }; PendingFetch.prototype.getResolvedPromise = function getResolvedPromise() { return this._resolvedDeferred.getPromise(); }; /** * A pending query is resolvable if it is already resolved or will be resolved * imminently (i.e. its subtracted query and the subtracted queries of all its * pending dependencies have been fetched). */ PendingFetch.prototype.isResolvable = function isResolvable() { if (this._fetchedSubtractedQuery) { return everyObject(this._pendingDependencyMap, function (pendingDependency) { return pendingDependency._fetchedSubtractedQuery; }); // Pending dependencies further down the graph either don't affect the // result or are already in `_pendingDependencyMap`. } return false; }; return PendingFetch; })(); function hasItems(map) { return !!_Object$keys(map).length; } /** * @internal * * Tracks pending (in-flight) queries. * * In order to send minimal queries and avoid re-retrieving data, * `RelayPendingQueryTracker` maintains a registry of pending queries, and * "subtracts" those from any new queries that callers enqueue. */ var RelayPendingQueryTracker = { /** * Used by `GraphQLQueryRunner` to enqueue new queries. */ add: function add(params) { return new PendingFetch(params); }, hasPendingQueries: function hasPendingQueries() { return hasItems(pendingFetchMap); }, /** * Clears all pending query tracking. Does not cancel the queries themselves. */ resetPending: function resetPending() { pendingFetchMap = {}; GraphQLDeferredQueryTracker.reset(); }, resolvePreloadQuery: function resolvePreloadQuery(queryID, result) { preloadQueryMap.resolveKey(queryID, result); }, rejectPreloadQuery: function rejectPreloadQuery(queryID, error) { preloadQueryMap.rejectKey(queryID, error); }, // TODO: Use `export type`. PendingFetch: PendingFetch }; module.exports = RelayPendingQueryTracker; /** * Error(s) in fetching/handleUpdate-ing its or one of its pending * dependency's subtracted query. There may be more than one error. However, * `_resolvedDeferred` is rejected with the earliest encountered error. */