From 1f07f71283c2c0bfa35244facab03cf11a2d9b46 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 28 Jan 2025 21:54:21 +0100 Subject: [PATCH] fix(core): cancel in-progress request when same value is assigned Fixes that `resource` wasn't cancelling its in-progress request if the same value as the current one is assigned. Fixes #59272. --- packages/core/src/resource/resource.ts | 6 ++- packages/core/test/resource/resource_spec.ts | 53 ++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/packages/core/src/resource/resource.ts b/packages/core/src/resource/resource.ts index 7392e511bf19..18ba9d0f7d2d 100644 --- a/packages/core/src/resource/resource.ts +++ b/packages/core/src/resource/resource.ts @@ -221,7 +221,11 @@ class ResourceImpl extends BaseWritableResource implements ResourceRef< } const current = untracked(this.value); - if (this.equal ? this.equal(current, value) : current === value) { + + if ( + untracked(this.status) === ResourceStatus.Local && + (this.equal ? this.equal(current, value) : current === value) + ) { return; } diff --git a/packages/core/test/resource/resource_spec.ts b/packages/core/test/resource/resource_spec.ts index aac793b9eff8..ef04b4d92385 100644 --- a/packages/core/test/resource/resource_spec.ts +++ b/packages/core/test/resource/resource_spec.ts @@ -622,6 +622,59 @@ describe('resource', () => { stream.set({error: 'fail'}); expect(res.value()).toBe(undefined); }); + + it('should interrupt pending request if the same value is set', async () => { + const counter = signal(0); + const backend = new MockEchoBackend<{counter: number} | null>(); + const aborted: ({counter: number} | null)[] = []; + const echoResource = resource<{counter: number} | null, {counter: number} | null>({ + request: () => ({counter: counter()}), + loader: ({request, abortSignal}) => { + abortSignal.addEventListener('abort', () => backend.abort(request)); + return backend.fetch(request).catch((reason) => { + if (reason === 'aborted') { + aborted.push(request); + } + throw new Error(reason); + }); + }, + injector: TestBed.inject(Injector), + }); + + // Start the initial load. + TestBed.flushEffects(); + await Promise.resolve(); + expect(echoResource.status()).toBe(ResourceStatus.Loading); + expect(echoResource.value()).toBe(undefined); + expect(echoResource.error()).toBe(undefined); + expect(aborted).toEqual([]); + + // Interrupt by setting a value before the request has resolved. + echoResource.set(null); + TestBed.flushEffects(); + await backend.flush(); + expect(echoResource.status()).toBe(ResourceStatus.Local); + expect(echoResource.value()).toBe(null); + expect(echoResource.error()).toBe(undefined); + expect(aborted).toEqual([{counter: 0}]); + + // Reload the resource to trigger another request. + echoResource.reload(); + TestBed.flushEffects(); + await Promise.resolve(); + expect(echoResource.status()).toBe(ResourceStatus.Reloading); + expect(echoResource.value()).toBe(null); + expect(echoResource.error()).toBe(undefined); + expect(aborted).toEqual([{counter: 0}]); + + // Interrupt the reload with the same value as before. + echoResource.set(null); + await backend.flush(); + expect(echoResource.status()).toBe(ResourceStatus.Local); + expect(echoResource.value()).toBe(null); + expect(echoResource.error()).toBe(undefined); + expect(aborted).toEqual([{counter: 0}, {counter: 0}]); + }); }); function flushMicrotasks(): Promise {