2
0
mirror of https://github.com/tenrok/axios.git synced 2026-06-17 19:21:29 +03:00

fix(xhr): unsubscribe cancelToken and signal on error, timeout, and abort paths (#10787)

* test(xhr): add regression for cancelToken and signal listener cleanup on error paths

* fix(xhr): unsubscribe cancelToken and signal on error, timeout, and abort paths

---------

Co-authored-by: Jay <jasonsaayman@gmail.com>
This commit is contained in:
iruizsalinas
2026-04-25 17:43:59 +02:00
committed by GitHub
parent cc9aa2f487
commit 640458daaf
2 changed files with 50 additions and 0 deletions
+4
View File
@@ -108,6 +108,7 @@ export default isXHRAdapterSupported &&
}
reject(new AxiosError('Request aborted', AxiosError.ECONNABORTED, config, request));
done();
// Clean up request
request = null;
@@ -123,6 +124,7 @@ export default isXHRAdapterSupported &&
// attach the underlying event for consumers who want details
err.event = event || null;
reject(err);
done();
request = null;
};
@@ -143,6 +145,7 @@ export default isXHRAdapterSupported &&
request
)
);
done();
// Clean up request
request = null;
@@ -192,6 +195,7 @@ export default isXHRAdapterSupported &&
}
reject(!cancel || cancel.type ? new CanceledError(null, config, request) : cancel);
request.abort();
done();
request = null;
};
+46
View File
@@ -170,4 +170,50 @@ describe('cancel (vitest browser)', () => {
const error = await promise.catch((thrown) => thrown);
expect(axios.isCancel(error)).toBe(true);
});
describe('listener cleanup on error paths', () => {
for (const { label, trigger } of [
{ label: 'network error', trigger: (r) => r.onerror(new Error('Network Error')) },
{ label: 'timeout', trigger: (r) => r.ontimeout() },
{ label: 'browser abort', trigger: (r) => r.onabort() },
]) {
it(`unsubscribes cancelToken listener after ${label}`, async () => {
const source = axios.CancelToken.source();
const promise = axios
.get('/foo/bar', { cancelToken: source.token })
.catch((thrown) => thrown);
const request = await waitForRequest();
trigger(request);
await promise;
expect(source.token._listeners || []).toEqual([]);
});
}
it('removes AbortSignal listener after network error', async () => {
const controller = new AbortController();
let listenerCount = 0;
const nativeAdd = controller.signal.addEventListener.bind(controller.signal);
const nativeRemove = controller.signal.removeEventListener.bind(controller.signal);
controller.signal.addEventListener = (type, fn, options) => {
if (type === 'abort') listenerCount++;
return nativeAdd(type, fn, options);
};
controller.signal.removeEventListener = (type, fn, options) => {
if (type === 'abort') listenerCount--;
return nativeRemove(type, fn, options);
};
const promise = axios
.get('/foo/bar', { signal: controller.signal })
.catch((thrown) => thrown);
const request = await waitForRequest();
request.onerror(new Error('Network Error'));
await promise;
expect(listenerCount).toBe(0);
});
});
});