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:
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user