@@ -20,7 +20,9 @@ const {
20
20
ReplaySubject,
21
21
createOperationDescriptor,
22
22
getRequest,
23
+ getRequestIdentifier,
23
24
Observable,
25
+ RelayFeatureFlags,
24
26
__internal : { fetchQueryDeduped} ,
25
27
} = require ( 'relay-runtime' ) ;
26
28
@@ -35,6 +37,7 @@ import type {
35
37
OperationType ,
36
38
GraphQLTaggedNode ,
37
39
GraphQLResponse ,
40
+ RequestIdentifier ,
38
41
} from 'relay-runtime' ;
39
42
40
43
let RenderDispatcher = null ;
@@ -83,25 +86,50 @@ function loadQuery<TQuery: OperationType, TEnvironmentProviderOptions>(
83
86
84
87
let unsubscribeFromNetworkRequest ;
85
88
let networkError = null ;
86
-
87
- // makeNetworkRequest will immediately start a raw network request and
88
- // return an Observable that when subscribing to it, will replay the
89
- // network events that have occured so far, as well as subsequent events.
89
+ // makeNetworkRequest will immediately start a raw network request if
90
+ // one isn't already in flight and return an Observable that when
91
+ // subscribed to will replay the network events that have occured so far,
92
+ // as well as subsequent events.
90
93
let madeNetworkRequest = false ;
91
94
const makeNetworkRequest = ( params ) : Observable < GraphQLResponse > => {
92
95
// N.B. this function is called synchronously or not at all
93
96
// madeNetworkRequest is safe to rely on in the returned value
94
-
97
+ // Even if the request gets deduped below, we still wan't to return an
98
+ // observable that provides the replayed network events for the query,
99
+ // so we set this to true before deduping, to guarantee that the
100
+ // `source` observable is returned.
95
101
madeNetworkRequest = true ;
96
- const network = environment . getNetwork ( ) ;
97
- const sourceObservable = network . execute (
98
- params ,
99
- variables ,
100
- networkCacheConfig ,
101
- ) ;
102
102
103
+ let observable ;
103
104
const subject = new ReplaySubject ( ) ;
104
- ( { unsubscribe : unsubscribeFromNetworkRequest } = sourceObservable . subscribe ( {
105
+ if ( RelayFeatureFlags . ENABLE_LOAD_QUERY_REQUEST_DEDUPING === true ) {
106
+ // Here, we are calling fetchQueryDeduped at the network layer level,
107
+ // which ensures that only a single network request is active for a given
108
+ // (environment, identifier) pair.
109
+ // Since network requests can be started /before/ we have the query ast
110
+ // necessary to process the results, we need to dedupe the raw requests
111
+ // separately from deduping the operation execution; specifically,
112
+ // if `loadQuery` is called multiple times before the query ast is available,
113
+ // we still want the network request to be deduped.
114
+ // - If a duplicate active network request is found, it will return an
115
+ // Observable that replays the events of the already active request.
116
+ // - If no duplicate active network request is found, it will call the fetchFn
117
+ // to start the request, and return an Observable that will replay
118
+ // the events from the network request.
119
+ // We provide an extra key to the identifier to distinguish deduping
120
+ // of raw network requests vs deduping of operation executions.
121
+ const identifier : RequestIdentifier =
122
+ 'raw-network-request-' + getRequestIdentifier ( params , variables ) ;
123
+ observable = fetchQueryDeduped ( environment , identifier , ( ) => {
124
+ const network = environment . getNetwork ( ) ;
125
+ return network . execute ( params , variables , networkCacheConfig ) ;
126
+ } ) ;
127
+ } else {
128
+ const network = environment . getNetwork ( ) ;
129
+ observable = network . execute ( params , variables , networkCacheConfig ) ;
130
+ }
131
+
132
+ ( { unsubscribe : unsubscribeFromNetworkRequest } = observable . subscribe ( {
105
133
error ( err ) {
106
134
networkError = err ;
107
135
subject . error ( err ) ;
@@ -150,12 +178,27 @@ function loadQuery<TQuery: OperationType, TEnvironmentProviderOptions>(
150
178
operation : OperationDescriptor ,
151
179
fetchFn : ( ) => Observable < GraphQLResponse > ,
152
180
) => {
153
- // N.B.
181
+ if ( RelayFeatureFlags . ENABLE_LOAD_QUERY_REQUEST_DEDUPING === true ) {
182
+ // N.B. at this point, if we're calling execute with a query ast (OperationDescriptor),
183
+ // we are guaranteed to have started a network request. We set this to
184
+ // true here as well since `makeNetworkRequest` might get skipped in the case
185
+ // where the query ast is already available and the query executions get deduped.
186
+ // Even if the execution gets deduped below, we still wan't to return
187
+ // an observable that provides the replayed network events for the query,
188
+ // so we set this to true before deduping, to guarantee that the `source`
189
+ // observable is returned.
190
+ madeNetworkRequest = true ;
191
+ }
192
+
154
193
// Here, we are calling fetchQueryDeduped, which ensures that only
155
194
// a single operation is active for a given (environment, identifier) pair,
156
195
// and also tracks the active state of the operation, which is necessary
157
196
// for our Suspense infra to later be able to suspend (or not) on
158
- // active operations.
197
+ // active operations. Even though we already dedupe raw network requests,
198
+ // we also need to dedupe and keep track operation execution for our Suspense
199
+ // infra, and we also want to avoid processing responses more than once, for
200
+ // the cases where `loadQuery` might be called multiple times after the query ast
201
+ // is available.
159
202
// - If a duplicate active operation is found, it will return an
160
203
// Observable that replays the events of the already active operation.
161
204
// - If no duplicate active operation is found, it will call the fetchFn
@@ -222,11 +265,11 @@ function loadQuery<TQuery: OperationType, TEnvironmentProviderOptions>(
222
265
checkAvailabilityAndExecute ( module ) ;
223
266
} else {
224
267
// If the module isn't synchronously available, we launch the
225
- // network request immediately and ignore the fetch policy.
226
- // Note that without the operation module we can't reliably
227
- // dedupe network requests, since the request identifier is
228
- // based on the variables the operation expects, and not
229
- // just the variables passed as input .
268
+ // network request immediately, ignoring the fetchPolicy. We
269
+ // do this because we can't check if a query is cached without the
270
+ // ast, and we know that if we don't have the query ast
271
+ // available, then this query could've never been written to the
272
+ // store in the first place, so it couldn't have been cached .
230
273
const networkObservable = makeNetworkRequest ( params ) ;
231
274
( { dispose : cancelOnLoadCallback } = PreloadableQueryRegistry . onLoad (
232
275
moduleId ,
0 commit comments