11import 'dart:async' ;
22
3+ import 'package:gql/ast.dart' ;
34import 'package:graphql/src/utilities/response.dart' ;
45import 'package:meta/meta.dart' ;
56import 'package:collection/collection.dart' ;
@@ -311,14 +312,26 @@ class QueryManager {
311312 // we attempt to resolve the from the cache
312313 if (shouldRespondEagerlyFromCache (options.fetchPolicy) &&
313314 ! queryResult.isOptimistic) {
314- final data = cache. readQuery (request, optimistic : false );
315- // we only push an eager query with data
316- if (data != null ) {
317- queryResult = QueryResult (
318- options : options,
319- data : data,
315+ final latestResult = _getQueryResultByRequest < TParsed > (request);
316+ if (latestResult != null && latestResult.data != null ) {
317+ // we have a result already cached + deserialized for this request
318+ // so we reuse it.
319+ // latest result won't be for loading, it must contain data
320+ queryResult = latestResult. copyWith (
320321 source: QueryResultSource .cache,
321322 );
323+ } else {
324+ // otherwise, we try to find the query in cache (which will require
325+ // deserialization)
326+ final data = cache.readQuery (request, optimistic: false );
327+ // we only push an eager query with data
328+ if (data != null ) {
329+ queryResult = QueryResult (
330+ options: options,
331+ data: data,
332+ source: QueryResultSource .cache,
333+ );
334+ }
322335 }
323336
324337 if (options.fetchPolicy == FetchPolicy .cacheOnly &&
@@ -358,6 +371,18 @@ class QueryManager {
358371 return queryResult;
359372 }
360373
374+ /// If a request already has a result associated with it in cache (as
375+ /// determined by [ObservableQuery.latestResult] ), we can return it without
376+ /// needing to denormalize + parse again.
377+ QueryResult <TParsed >? _getQueryResultByRequest <TParsed >(Request request) {
378+ for (final query in queries.values) {
379+ if (query.options.asRequest == request) {
380+ return query.latestResult as QueryResult <TParsed >? ;
381+ }
382+ }
383+ return null ;
384+ }
385+
361386 /// Refetch the [ObservableQuery] referenced by [queryId] ,
362387 /// overriding any present non-network-only [FetchPolicy] .
363388 Future <QueryResult <TParsed >?> refetchQuery <TParsed >(String queryId) {
@@ -383,11 +408,11 @@ class QueryManager {
383408 return results;
384409 }
385410
386- ObservableQuery <TParsed >? getQuery <TParsed >(String ? queryId) {
387- if (! queries.containsKey (queryId)) {
411+ ObservableQuery <TParsed >? getQuery <TParsed >(final String ? queryId) {
412+ if (! queries.containsKey (queryId) || queryId == null ) {
388413 return null ;
389414 }
390- final query = queries[queryId! ];
415+ final query = queries[queryId];
391416 if (query is ObservableQuery <TParsed >) {
392417 return query;
393418 }
@@ -402,16 +427,17 @@ class QueryManager {
402427 void addQueryResult <TParsed >(
403428 Request request,
404429 String ? queryId,
405- QueryResult <TParsed > queryResult,
406- ) {
430+ QueryResult <TParsed > queryResult, {
431+ bool fromRebroadcast = false ,
432+ }) {
407433 final observableQuery = getQuery <TParsed >(queryId);
408434
409435 if (observableQuery != null && ! observableQuery.controller.isClosed) {
410- observableQuery.addResult (queryResult);
436+ observableQuery.addResult (queryResult, fromRebroadcast : fromRebroadcast );
411437 }
412438 }
413439
414- /// Create an optimstic result for the query specified by `queryId` , if it exists
440+ /// Create an optimistic result for the query specified by `queryId` , if it exists
415441 QueryResult <TParsed > _getOptimisticQueryResult <TParsed >(
416442 Request request, {
417443 required String queryId,
@@ -463,27 +489,55 @@ class QueryManager {
463489 return false ;
464490 }
465491
466- final shouldBroadast = cache.shouldBroadcast (claimExecution: true );
492+ final shouldBroadcast = cache.shouldBroadcast (claimExecution: true );
467493
468- if (! shouldBroadast && ! force) {
494+ if (! shouldBroadcast && ! force) {
469495 return false ;
470496 }
471497
472- for (var query in queries.values) {
473- if (query != exclude && query.isRebroadcastSafe) {
498+ // If two ObservableQueries are backed by the same [Request], we only need
499+ // to [readQuery] for it once.
500+ final Map <Request , QueryResult <Object ?>> diffQueryResultCache = {};
501+ final Map <Request , bool > ignoreQueryResults = {};
502+ for (final query in queries.values) {
503+ final Request request = query.options.asRequest;
504+ final cachedQueryResult = diffQueryResultCache[request];
505+ if (query == exclude || ! query.isRebroadcastSafe) {
506+ continue ;
507+ }
508+ if (cachedQueryResult != null ) {
509+ // We've already done the diff and denormalized, emit to the observable
510+ addQueryResult (
511+ request,
512+ query.queryId,
513+ cachedQueryResult,
514+ fromRebroadcast: true ,
515+ );
516+ } else if (ignoreQueryResults.containsKey (request)) {
517+ // We've already seen this one and don't need to notify
518+ continue ;
519+ } else {
520+ // We haven't seen this one yet, denormalize from cache and diff
474521 final cachedData = cache.readQuery (
475522 query.options.asRequest,
476523 optimistic: query.options.policies.mergeOptimisticData,
477524 );
478525 if (_cachedDataHasChangedFor (query, cachedData)) {
479- query.addResult (
480- mapFetchResultToQueryResult (
481- Response (data: cachedData, response: {}),
482- query.options,
483- source: QueryResultSource .cache,
484- ),
526+ // The data has changed
527+ final queryResult = QueryResult (
528+ data: cachedData,
529+ options: query.options,
530+ source: QueryResultSource .cache,
531+ );
532+ diffQueryResultCache[request] = queryResult;
533+ addQueryResult (
534+ request,
535+ query.queryId,
536+ queryResult,
485537 fromRebroadcast: true ,
486538 );
539+ } else {
540+ ignoreQueryResults[request] = true ;
487541 }
488542 }
489543 }
0 commit comments