Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

(fix) - Fix timing for out-of-band client.reexecuteOperation calls (#860)

* Fix timing for out-of-band client.reexecuteOperation calls

When an operation becomes asynchronous, by matter of an exchange
delaying it, while it also calls client.reexecuteOpoeration, we
were calling client.reexecuteOperation out-of-band, which causes
the reexecuted operation to be dispatched immediately.
We can't detect the intent of the exchange, and don't know that it
may afterwards immediately send a result synchronously after the delay,
but we do know that once an operation becomes asynchronous, the timing
of client.reexecuteOperation is not sensitive anymore. So instead we
now choose to defer it by a tick.

* Add Changeset

authored by

Phil Plückthun and committed by
GitHub
f468f6df 0a90257b

+27 -18
+5
.changeset/strong-games-scream.md
··· 1 + --- 2 + '@urql/core': patch 3 + --- 4 + 5 + Fix timing for out-of-band `client.reexecuteOperation` calls. This would surface in asynchronous caching scenarios, where no result would be delivered by the cache synchronously, while it still calls `client.reexecuteOperation` for e.g. a `network-only` request, which happens for `cache-and-network`. This issue becomes especially obvious in highly synchronous frameworks like Svelte.
+17 -18
packages/core/src/client.ts
··· 41 41 toSuspenseSource, 42 42 withPromise, 43 43 maskTypename, 44 + scheduleTask, 44 45 noop, 45 46 } from './utils'; 46 47 ··· 74 75 75 76 /** The URQL application-wide client library. Each execute method starts a GraphQL request and returns a stream of results. */ 76 77 export class Client { 78 + /** Start an operation from an exchange */ 79 + reexecuteOperation: (operation: Operation) => void; 80 + 77 81 // Event target for monitoring 78 82 subscribeToDebugTarget?: (onEvent: (e: DebugEvent) => void) => Subscription; 79 83 ··· 121 125 >(); 122 126 this.operations$ = operations$; 123 127 124 - let isDispatching = false; 128 + let isOperationBatchActive = false; 125 129 this.dispatchOperation = (operation?: Operation) => { 126 - if (!isDispatching) { 127 - isDispatching = true; 128 - if (operation) nextOperation(operation); 129 - let queued: Operation | void; 130 - while ((queued = this.queue.shift())) nextOperation(queued); 131 - isDispatching = false; 132 - } else if (operation) { 133 - nextOperation(operation); 130 + isOperationBatchActive = true; 131 + if (operation) nextOperation(operation); 132 + while ((operation = this.queue.shift())) nextOperation(operation); 133 + isOperationBatchActive = false; 134 + }; 135 + 136 + this.reexecuteOperation = (operation: Operation) => { 137 + // Reexecute operation only if any subscribers are still subscribed to the 138 + // operation's exchange results 139 + if ((this.activeOperations[operation.key] || 0) > 0) { 140 + this.queue.push(operation); 141 + if (!isOperationBatchActive) scheduleTask(this.dispatchOperation); 134 142 } 135 143 }; 136 144 ··· 250 258 ? toSuspenseSource<OperationResult>(result$ as Source<OperationResult>) 251 259 : (result$ as Source<OperationResult>); 252 260 } 253 - 254 - reexecuteOperation = (operation: Operation) => { 255 - // Reexecute operation only if any subscribers are still subscribed to the 256 - // operation's exchange results 257 - if ((this.activeOperations[operation.key] || 0) > 0) { 258 - this.queue.push(operation); 259 - this.dispatchOperation(); 260 - } 261 - }; 262 261 263 262 query<Data = any, Variables extends object = {}>( 264 263 query: DocumentNode | string,
+4
packages/core/src/utils/defer.ts
··· 1 + export const scheduleTask: (fn: () => void) => void = 2 + typeof Promise !== 'undefined' 3 + ? Promise.prototype.then.bind(Promise.resolve()) 4 + : fn => setTimeout(fn, 0);
+1
packages/core/src/utils/index.ts
··· 6 6 export * from './stringifyVariables'; 7 7 export * from './maskTypename'; 8 8 export * from './withPromise'; 9 + export * from './defer'; 9 10 10 11 export const noop = () => { 11 12 /* noop */