Skip to content

Commit

Permalink
Add a new option to pass an abort signal to requests
Browse files Browse the repository at this point in the history
This allows developers to abort requests if responses are not recieved in sufficient time

Can be used to set a custom timeout per request

If the request is aborted an error will be thrown

update docs
  • Loading branch information
lizkenyon committed Jan 16, 2025
1 parent 5c748ed commit bf7cf8b
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 2 deletions.
39 changes: 37 additions & 2 deletions packages/apps/shopify-api/docs/reference/clients/Graphql.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ const response = await client.request(
console.log(response.data, response.extensions, response.headers);
```

> **Note**: If using TypeScript, you can pass in a type argument for the response body:
#### Using a type argument

If using TypeScript, you can pass in a type argument for the response body:

```ts
// If using TypeScript, you can type the response body
Expand All @@ -110,7 +112,9 @@ const response = await client.request<MyResponseBodyType>(/* ... */);
console.log(response.body.data);
```

> **Note**: If there are any errors in the response, `request` will throw a `GraphqlQueryError` which includes details from the API response:
#### Handling errors

If there are any errors in the response, `request` will throw a `GraphqlQueryError` which includes details from the API response:

```ts
import {GraphqlQueryError} from '@shopify/shopify-api';
Expand All @@ -130,6 +134,31 @@ try {
}
```

#### Setting a timeout
You can set a timeout for the request by passing in a signal with an AbortController. If the request takes longer than the timeout, it will be aborted and an AbortError will be thrown.

```ts
const response = await client.request(
`query GetProducts($first: Int!) {
products (first: $first) {
edges {
node {
id
title
descriptionHtml
}
}
}
}`,
{
variables: {
first: 10,
},
signal: AbortSignal.timeout(3000), // 3 seconds
},
);
```

### Parameters

#### operation
Expand All @@ -156,6 +185,12 @@ Add custom headers to the request.

The maximum number of times to retry the request.

#### options.signal

`AbortSignal`

An optional AbortSignal to cancel the request.

### Return

`Promise<GraphQLClientResponse>`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,4 +319,16 @@ describe('GraphQL client', () => {
),
);
});

it('respects the abort signal', async () => {
const shopify = shopifyApi(testConfig());
const client = new shopify.clients.Graphql({session});
const controller = new AbortController();

controller.abort();

await expect(
client.request(QUERY, {signal: controller.signal}),
).rejects.toThrow(HttpRequestError);
});
});
4 changes: 4 additions & 0 deletions packages/apps/shopify-api/lib/clients/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ export interface GraphqlQueryOptions<
* The maximum number of times to retry the request if it fails with a throttling or server error.
*/
retries?: number;
/**
* An optional AbortSignal to cancel the request.
*/
signal?: AbortSignal;
}

export {GraphqlClient} from './admin/graphql/client';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ describe('admin.authenticate context', () => {
).rejects.toThrowError(HttpMaxRetriesError);
});

it('respects the abort signal', async () => {
// GIVEN
const {admin} = await setUpEmbeddedFlow();
const controller = new AbortController();
await mockGraphqlRequest()({status: 200});

// Abort the request immediately
controller.abort();

// WHEN/THEN
await expect(
admin.graphql('{ shop { name } }', {signal: controller.signal}),
).rejects.toThrowError(Error);
});

it('re-throws errors other than HttpResponseErrors on REST requests', async () => {
// GIVEN
const {admin} = await setUpEmbeddedFlow();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export function graphqlClientFactory({
variables: options?.variables,
retries: options?.tries ? options.tries - 1 : 0,
headers: options?.headers,
signal: options?.signal,
});

return new Response(JSON.stringify(apiResponse));
Expand Down
5 changes: 5 additions & 0 deletions packages/apps/shopify-app-remix/src/server/clients/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ export interface GraphQLQueryOptions<
* The total number of times to try the request if it fails.
*/
tries?: number;

/**
* An optional AbortSignal to cancel the request.
*/
signal?: AbortSignal;
}

export type GraphQLResponse<
Expand Down

0 comments on commit bf7cf8b

Please sign in to comment.