Skip to content

Commit 2537c96

Browse files
committed
do not overwrite own AbortSignal if an external AbortSignal is passed in
1 parent d2a60d4 commit 2537c96

File tree

2 files changed

+31
-6
lines changed

2 files changed

+31
-6
lines changed

.changeset/quiet-balloons-wave.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@apollo/client": patch
3+
---
4+
5+
Fix a situation where a passed-in `AbortSignal` would override internal unsubscription cancellation behaviour.

src/link/http/createHttpLink.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ import { serializeFetchParameter } from "./serializeFetchParameter.js";
2929

3030
const backupFetch = maybe(() => fetch);
3131

32+
function noop() {}
33+
3234
export const createHttpLink = (linkOptions: HttpLink.Options = {}) => {
3335
let {
3436
uri = "/graphql",
@@ -106,11 +108,29 @@ export const createHttpLink = (linkOptions: HttpLink.Options = {}) => {
106108
);
107109
}
108110

109-
let controller: AbortController | undefined;
110-
if (!options.signal && typeof AbortController !== "undefined") {
111-
controller = new AbortController();
112-
options.signal = controller.signal;
111+
let controller: AbortController | undefined = new AbortController();
112+
let cleanupController = () => {
113+
controller = undefined;
114+
};
115+
if (options.signal) {
116+
// in an ideal world we could use `AbortSignal.any` here, but
117+
// React Native uses https://github.com/mysticatea/abort-controller as
118+
// a polyfill for `AbortController`, and it does not support `AbortSignal.any`.
119+
const abort = controller.abort.bind(controller);
120+
options.signal.addEventListener("abort", abort, { once: true });
121+
cleanupController = () => {
122+
controller = undefined;
123+
// on cleanup, we need to stop listening to `options.signal` to avoid memory leaks
124+
options.signal.removeEventListener("abort", abort);
125+
cleanupController = noop;
126+
};
127+
// react native also does not support the addEventListener `signal` option
128+
// so we have to simulate that ourself
129+
controller.signal.addEventListener("abort", cleanupController, {
130+
once: true,
131+
});
113132
}
133+
options.signal = controller.signal;
114134

115135
// If requested, set method to GET if there are no mutations.
116136
const definitionIsMutation = (d: DefinitionNode) => {
@@ -181,11 +201,11 @@ export const createHttpLink = (linkOptions: HttpLink.Options = {}) => {
181201
}
182202
})
183203
.then(() => {
184-
controller = undefined;
204+
cleanupController();
185205
observer.complete();
186206
})
187207
.catch((err) => {
188-
controller = undefined;
208+
cleanupController();
189209
handleError(err, observer);
190210
});
191211

0 commit comments

Comments
 (0)