Skip to content

Commit

Permalink
Add support for AbortController signal passed via context
Browse files Browse the repository at this point in the history
  • Loading branch information
marioeg-stripe committed Apr 27, 2022
1 parent cde5f56 commit f1fa6ac
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 0 deletions.
72 changes: 72 additions & 0 deletions src/__tests__/restLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4207,4 +4207,76 @@ describe('Playing nice with others', () => {
authors: { message: 'Your query was bad and you should feel bad!' },
});
});

it('should fulfill fetch when an AbortController signal is passed but never aborted', done => {
fetchMock.get('/api/posts', posts);
const link = new RestLink({ uri: '/api' });

const query = gql`
query {
people @rest(type: "[Post]", path: "/posts") {
title
}
}
`;

const controller = new AbortController();
const reqPromise = toPromise<Result>(
execute(link, {
operationName: 'abortQuery',
query,
context: { fetchOptions: { signal: controller.signal } },
}),
);

const timeout = setTimeout(() => {
done('timeout should never run');
}, 100);

return reqPromise.then(res => {
clearTimeout(timeout);
expect(res.data).toEqual({
people: [
{ title: 'Love apollo', __typename: 'Post' },
{ title: 'Respect apollo', __typename: 'Post' },
],
});
done();
});
});

it('should cancel fetch when an AbortController signal is passed and aborted', done => {
fetchMock.get('/api/posts', posts);
const link = new RestLink({ uri: '/api' });

const query = gql`
query {
people @rest(ftype: "[Post]", path: "/posts") {
title
}
}
`;

const controller = new AbortController();
const reqPromise = toPromise<Result>(
execute(link, {
operationName: 'abortQuery',
query,
context: { fetchOptions: { signal: controller.signal } },
}),
);
controller.abort();

let reqData = null;
const timeout = setTimeout(() => {
expect(reqData).toBeNull();
done();
}, 100);

return reqPromise.then(res => {
clearTimeout(timeout);
reqData = res.data;
done('fetch request should not resolve');
});
});
});
12 changes: 12 additions & 0 deletions src/restLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -814,6 +814,11 @@ interface LinkChainContext {

/** An array of the responses from each fetched URL, useful for accessing headers in earlier links */
restResponses?: Response[];

/** Overrides some fetch options arguments passed to the fetch call */
fetchOptions?: {
signal?: AbortSignal;
};
}

/** Context passed via graphql() to our resolver */
Expand All @@ -840,6 +845,8 @@ interface RequestContext {

/** An array of the responses from each fetched URL */
responses: Response[];

signal?: AbortSignal;
}

const addTypeToNode = (node, typename) => {
Expand Down Expand Up @@ -924,6 +931,7 @@ const resolver: Resolver = async (
fieldNameNormalizer: linkLevelNameNormalizer,
fieldNameDenormalizer: linkLevelNameDenormalizer,
serializers,
signal,
responseTransformer,
} = context;

Expand Down Expand Up @@ -1060,6 +1068,7 @@ const resolver: Resolver = async (
// Only set credentials if they're non-null as some browsers throw an exception:
// https://github.com/apollographql/apollo-link-rest/issues/121#issuecomment-396049677
...(credentials ? { credentials } : {}),
...(signal ? { signal } : {}),
};
const requestUrl = `${endpointOption.uri}${pathWithParams}`;

Expand Down Expand Up @@ -1332,6 +1341,8 @@ export class RestLink extends ApolloLink {

const credentials: RequestCredentials =
context.credentials || this.credentials;
const signal: AbortSignal | undefined =
context.fetchOptions != null ? context.fetchOptions.signal : undefined;

const queryWithTypename = addTypenameToDocument(query);

Expand All @@ -1355,6 +1366,7 @@ export class RestLink extends ApolloLink {
fragmentDefinitions,
typePatcher: this.typePatcher,
serializers: this.serializers,
signal,
responses: [],
responseTransformer: this.responseTransformer,
};
Expand Down

0 comments on commit f1fa6ac

Please sign in to comment.