diff --git a/.changeset/silent-monkeys-double.md b/.changeset/silent-monkeys-double.md new file mode 100644 index 00000000000..59d85561f0b --- /dev/null +++ b/.changeset/silent-monkeys-double.md @@ -0,0 +1,5 @@ +--- +"@apollo/client": patch +--- + +fix: modify BatchHttpLink to have a separate timer for each different batch key diff --git a/src/link/batch/__tests__/batchLink.ts b/src/link/batch/__tests__/batchLink.ts index 844ad375dc6..eae15faa851 100644 --- a/src/link/batch/__tests__/batchLink.ts +++ b/src/link/batch/__tests__/batchLink.ts @@ -314,6 +314,74 @@ describe('OperationBatcher', () => { } }); + itAsync('should be able to consume from a queue containing multiple queries with different batch keys', (resolve, reject) => { + // NOTE: this test was added to ensure that queries don't "hang" when consumed by BatchLink. + // "Hanging" in this case results in this test never resolving. So + // if this test times out it's probably a real issue and not a flake + const request2: Operation = createOperation( + {}, + { + query, + }, + ); + + const BH = createMockBatchHandler( + { + request: { query }, + result: { data }, + }, + { + request: { query }, + result: { data }, + }, + ); + + let key = true; + const batchKey = () => { + key = !key; + return '' + !key; + }; + + const myBatcher = new OperationBatcher({ + batchInterval: 10, + batchMax: 10, + batchHandler: BH, + batchKey + }); + + const observable1 = myBatcher.enqueueRequest({ operation }); + const observable2 = myBatcher.enqueueRequest({ operation: request2 }); + + let notify = false; + observable1.subscribe(resultObj1 => { + try { + expect(resultObj1).toEqual({ data }); + } catch (e) { + reject(e); + } + + if (notify) { + resolve(); + } else { + notify = true; + } + }); + + observable2.subscribe(resultObj2 => { + try { + expect(resultObj2).toEqual({ data }); + } catch (e) { + reject(e); + } + + if (notify) { + resolve(); + } else { + notify = true; + } + }); + }); + itAsync('should return a promise when we enqueue a request and resolve it with a result', (resolve, reject) => { const BH = createMockBatchHandler({ request: { query }, diff --git a/src/link/batch/batching.ts b/src/link/batch/batching.ts index 6edee97624c..7046aa5fe57 100644 --- a/src/link/batch/batching.ts +++ b/src/link/batch/batching.ts @@ -32,7 +32,7 @@ export class OperationBatcher { // Queue on which the QueryBatcher will operate on a per-tick basis. private batchesByKey = new Map(); - private scheduledBatchTimer: ReturnType; + private scheduledBatchTimerByKey = new Map>(); private batchDebounce?: boolean; private batchInterval?: number; private batchMax: number; @@ -211,9 +211,10 @@ export class OperationBatcher { } private scheduleQueueConsumption(key: string): void { - clearTimeout(this.scheduledBatchTimer); - this.scheduledBatchTimer = setTimeout(() => { + clearTimeout(this.scheduledBatchTimerByKey.get(key)); + this.scheduledBatchTimerByKey.set(key, setTimeout(() => { this.consumeQueue(key); - }, this.batchInterval); + this.scheduledBatchTimerByKey.delete(key); + }, this.batchInterval)); } }