Skip to content

Commit 054ffa7

Browse files
committed
do not overwrite own AbortSignal if an external AbortSignal is passed in
1 parent 07bfe0d commit 054ffa7

2 files changed

Lines changed: 30 additions & 6 deletions

File tree

.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/BaseHttpLink.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
import { selectURI } from "./selectURI.js";
2626

2727
const backupFetch = maybe(() => fetch);
28+
function noop() {}
2829

2930
export class BaseHttpLink extends ApolloLink {
3031
constructor(linkOptions: HttpLink.Options = {}) {
@@ -89,11 +90,29 @@ export class BaseHttpLink extends ApolloLink {
8990
);
9091
}
9192

92-
let controller: AbortController | undefined;
93-
if (!options.signal && typeof AbortController !== "undefined") {
94-
controller = new AbortController();
95-
options.signal = controller.signal;
93+
let controller: AbortController | undefined = new AbortController();
94+
let cleanupController = () => {
95+
controller = undefined;
96+
};
97+
if (options.signal) {
98+
// in an ideal world we could use `AbortSignal.any` here, but
99+
// React Native uses https://github.com/mysticatea/abort-controller as
100+
// a polyfill for `AbortController`, and it does not support `AbortSignal.any`.
101+
const abort = controller.abort.bind(controller);
102+
options.signal.addEventListener("abort", abort, { once: true });
103+
cleanupController = () => {
104+
controller = undefined;
105+
// on cleanup, we need to stop listening to `options.signal` to avoid memory leaks
106+
options.signal.removeEventListener("abort", abort);
107+
cleanupController = noop;
108+
};
109+
// react native also does not support the addEventListener `signal` option
110+
// so we have to simulate that ourself
111+
controller.signal.addEventListener("abort", cleanupController, {
112+
once: true,
113+
});
96114
}
115+
options.signal = controller.signal;
97116

98117
if (useGETForQueries && !isMutationOperation(operation.query)) {
99118
options.method = "GET";
@@ -132,11 +151,11 @@ export class BaseHttpLink extends ApolloLink {
132151
}
133152
})
134153
.then(() => {
135-
controller = undefined;
154+
cleanupController();
136155
observer.complete();
137156
})
138157
.catch((err) => {
139-
controller = undefined;
158+
cleanupController();
140159
observer.error(err);
141160
});
142161

0 commit comments

Comments
 (0)