Skip to content

Commit

Permalink
Add retry option (#54)
Browse files Browse the repository at this point in the history
* add: retry option

* doc: retry option

* chore: increment minor version

* chore: add comment about retry handling

* refactor: Constantized the minimum timeout value.

* chore: Accurately expressed the message for when retrying
  • Loading branch information
Sinhalite authored May 8, 2023
1 parent c4772f8 commit 7077435
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 40 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { createClient } from 'microcms-js-sdk'; //ES6
const client = createClient({
serviceDomain: "YOUR_DOMAIN", // YOUR_DOMAIN is the XXXX part of XXXX.microcms.io
apiKey: "YOUR_API_KEY",
// retry: true // Retry attempts up to a maximum of two times.
});
```

Expand All @@ -49,6 +50,7 @@ const { createClient } = microcms;
const client = createClient({
serviceDomain: "YOUR_DOMAIN", // YOUR_DOMAIN is the XXXX part of XXXX.microcms.io
apiKey: "YOUR_API_KEY",
// retry: true // Retry attempts up to a maximum of two times.
// customFetcher: fetch.bind(globalThis), // Provide a custom `fetch` implementation as an option
});
</script>
Expand Down
28 changes: 28 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "microcms-js-sdk",
"version": "2.3.3",
"version": "2.4.0",
"description": "JavaScript SDK Client for microCMS.",
"main": "./dist/cjs/microcms-js-sdk.js",
"module": "./dist/esm/microcms-js-sdk.js",
Expand Down Expand Up @@ -29,6 +29,7 @@
"dist"
],
"dependencies": {
"async-retry": "^1.3.3",
"cross-fetch": "^3.1.5",
"encoding": "^0.1.13",
"qs": "^6.10.1"
Expand All @@ -38,6 +39,7 @@
"@rollup/plugin-commonjs": "^19.0.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^13.0.0",
"@types/async-retry": "^1.4.5",
"@types/jest": "^28.1.6",
"@types/node": "^15.0.2",
"@types/qs": "^6.9.6",
Expand Down
117 changes: 79 additions & 38 deletions src/createClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,14 @@ import {
UpdateRequest,
DeleteRequest,
} from './types';
import { API_VERSION, BASE_DOMAIN } from './utils/constants';
import {
API_VERSION,
BASE_DOMAIN,
MAX_RETRY_COUNT,
MIN_TIMEOUT_MS,
} from './utils/constants';
import { generateFetchClient } from './lib/fetch';
import retry from 'async-retry';

/**
* Initialize SDK Client
Expand All @@ -29,6 +35,7 @@ export const createClient = ({
serviceDomain,
apiKey,
customFetch,
retry: retryOption,
}: MicroCMSClient) => {
if (!serviceDomain || !apiKey) {
throw new Error('parameter is required (check serviceDomain and apiKey)');
Expand All @@ -55,53 +62,87 @@ export const createClient = ({
customBody,
}: MakeRequest) => {
const fetchClient = generateFetchClient(apiKey, customFetch);

const queryString = parseQuery(queries);
const url = `${baseUrl}/${endpoint}${contentId ? `/${contentId}` : ''}${
queryString ? `?${queryString}` : ''
}`;

try {
const response = await fetchClient(url, {
method: method || 'GET',
headers: customHeaders,
body: customBody,
});

if (!response.ok) {
const message = await (async () => {
// Enclose `response.json()` in a try since it may throw an error
// Only return the `message` if there is a `message`
try {
const { message } = await response.json();
return message ?? null;
} catch (_) {
return null;
}
})();
return Promise.reject(
new Error(
`fetch API response status: ${response.status}${
message ? `\n message is \`${message}\`` : ''
}`
)
);
const getMessageFromResponse = async (response: Response) => {
// Enclose `response.json()` in a try since it may throw an error
// Only return the `message` if there is a `message`
try {
const { message } = await response.json();
return message ?? null;
} catch (_) {
return null;
}
};

if (method === 'DELETE') return;
return await retry(
async (bail) => {
try {
const response = await fetchClient(url, {
method: method || 'GET',
headers: customHeaders,
body: customBody,
});

return response.json();
} catch (error) {
if (error.data) {
throw error.data;
}
// If a status code in the 400 range other than 429 is returned, do not retry.
if (
response.status !== 429 &&
response.status >= 400 &&
response.status < 500
) {
const message = await getMessageFromResponse(response);

if (error.response?.data) {
throw error.response.data;
}
return bail(
new Error(
`fetch API response status: ${response.status}${
message ? `\n message is \`${message}\`` : ''
}`
)
);
}

return Promise.reject(new Error(`Network Error.\n Details: ${error}`));
}
// If the response fails with any other status code, retry until the set number of attempts is reached.
if (!response.ok) {
const message = await getMessageFromResponse(response);

return Promise.reject(
new Error(
`fetch API response status: ${response.status}${
message ? `\n message is \`${message}\`` : ''
}`
)
);
}

if (method === 'DELETE') return;

return response.json();
} catch (error) {
if (error.data) {
throw error.data;
}

if (error.response?.data) {
throw error.response.data;
}

return Promise.reject(
new Error(`Network Error.\n Details: ${error}`)
);
}
},
{
retries: retryOption ? MAX_RETRY_COUNT : 0,
onRetry: (err, num) => {
console.log(err);
console.log(`Waiting for retry (${num}/${MAX_RETRY_COUNT})`);
},
minTimeout: MIN_TIMEOUT_MS,
}
);
};

/**
Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface MicroCMSClient {
serviceDomain: string;
apiKey: string;
customFetch?: Fetch;
retry?: boolean;
}

type depthNumber = 1 | 2 | 3;
Expand All @@ -26,7 +27,7 @@ export interface MicroCMSQueries {
depth?: depthNumber;
ids?: string | string[];
filters?: string;
richEditorFormat?: 'html'|'object';
richEditorFormat?: 'html' | 'object';
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export const BASE_DOMAIN = 'microcms.io';
export const API_VERSION = 'v1';
export const MAX_RETRY_COUNT = 2;
export const MIN_TIMEOUT_MS = 5000;
Loading

0 comments on commit 7077435

Please sign in to comment.