Skip to content

Commit 7077435

Browse files
authored
Add retry option (#54)
* 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
1 parent c4772f8 commit 7077435

File tree

7 files changed

+256
-40
lines changed

7 files changed

+256
-40
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import { createClient } from 'microcms-js-sdk'; //ES6
3636
const client = createClient({
3737
serviceDomain: "YOUR_DOMAIN", // YOUR_DOMAIN is the XXXX part of XXXX.microcms.io
3838
apiKey: "YOUR_API_KEY",
39+
// retry: true // Retry attempts up to a maximum of two times.
3940
});
4041
```
4142

@@ -49,6 +50,7 @@ const { createClient } = microcms;
4950
const client = createClient({
5051
serviceDomain: "YOUR_DOMAIN", // YOUR_DOMAIN is the XXXX part of XXXX.microcms.io
5152
apiKey: "YOUR_API_KEY",
53+
// retry: true // Retry attempts up to a maximum of two times.
5254
// customFetcher: fetch.bind(globalThis), // Provide a custom `fetch` implementation as an option
5355
});
5456
</script>

package-lock.json

Lines changed: 28 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "microcms-js-sdk",
3-
"version": "2.3.3",
3+
"version": "2.4.0",
44
"description": "JavaScript SDK Client for microCMS.",
55
"main": "./dist/cjs/microcms-js-sdk.js",
66
"module": "./dist/esm/microcms-js-sdk.js",
@@ -29,6 +29,7 @@
2929
"dist"
3030
],
3131
"dependencies": {
32+
"async-retry": "^1.3.3",
3233
"cross-fetch": "^3.1.5",
3334
"encoding": "^0.1.13",
3435
"qs": "^6.10.1"
@@ -38,6 +39,7 @@
3839
"@rollup/plugin-commonjs": "^19.0.0",
3940
"@rollup/plugin-json": "^4.1.0",
4041
"@rollup/plugin-node-resolve": "^13.0.0",
42+
"@types/async-retry": "^1.4.5",
4143
"@types/jest": "^28.1.6",
4244
"@types/node": "^15.0.2",
4345
"@types/qs": "^6.9.6",

src/createClient.ts

Lines changed: 79 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,14 @@ import {
1919
UpdateRequest,
2020
DeleteRequest,
2121
} from './types';
22-
import { API_VERSION, BASE_DOMAIN } from './utils/constants';
22+
import {
23+
API_VERSION,
24+
BASE_DOMAIN,
25+
MAX_RETRY_COUNT,
26+
MIN_TIMEOUT_MS,
27+
} from './utils/constants';
2328
import { generateFetchClient } from './lib/fetch';
29+
import retry from 'async-retry';
2430

2531
/**
2632
* Initialize SDK Client
@@ -29,6 +35,7 @@ export const createClient = ({
2935
serviceDomain,
3036
apiKey,
3137
customFetch,
38+
retry: retryOption,
3239
}: MicroCMSClient) => {
3340
if (!serviceDomain || !apiKey) {
3441
throw new Error('parameter is required (check serviceDomain and apiKey)');
@@ -55,53 +62,87 @@ export const createClient = ({
5562
customBody,
5663
}: MakeRequest) => {
5764
const fetchClient = generateFetchClient(apiKey, customFetch);
58-
5965
const queryString = parseQuery(queries);
6066
const url = `${baseUrl}/${endpoint}${contentId ? `/${contentId}` : ''}${
6167
queryString ? `?${queryString}` : ''
6268
}`;
6369

64-
try {
65-
const response = await fetchClient(url, {
66-
method: method || 'GET',
67-
headers: customHeaders,
68-
body: customBody,
69-
});
70-
71-
if (!response.ok) {
72-
const message = await (async () => {
73-
// Enclose `response.json()` in a try since it may throw an error
74-
// Only return the `message` if there is a `message`
75-
try {
76-
const { message } = await response.json();
77-
return message ?? null;
78-
} catch (_) {
79-
return null;
80-
}
81-
})();
82-
return Promise.reject(
83-
new Error(
84-
`fetch API response status: ${response.status}${
85-
message ? `\n message is \`${message}\`` : ''
86-
}`
87-
)
88-
);
70+
const getMessageFromResponse = async (response: Response) => {
71+
// Enclose `response.json()` in a try since it may throw an error
72+
// Only return the `message` if there is a `message`
73+
try {
74+
const { message } = await response.json();
75+
return message ?? null;
76+
} catch (_) {
77+
return null;
8978
}
79+
};
9080

91-
if (method === 'DELETE') return;
81+
return await retry(
82+
async (bail) => {
83+
try {
84+
const response = await fetchClient(url, {
85+
method: method || 'GET',
86+
headers: customHeaders,
87+
body: customBody,
88+
});
9289

93-
return response.json();
94-
} catch (error) {
95-
if (error.data) {
96-
throw error.data;
97-
}
90+
// If a status code in the 400 range other than 429 is returned, do not retry.
91+
if (
92+
response.status !== 429 &&
93+
response.status >= 400 &&
94+
response.status < 500
95+
) {
96+
const message = await getMessageFromResponse(response);
9897

99-
if (error.response?.data) {
100-
throw error.response.data;
101-
}
98+
return bail(
99+
new Error(
100+
`fetch API response status: ${response.status}${
101+
message ? `\n message is \`${message}\`` : ''
102+
}`
103+
)
104+
);
105+
}
102106

103-
return Promise.reject(new Error(`Network Error.\n Details: ${error}`));
104-
}
107+
// If the response fails with any other status code, retry until the set number of attempts is reached.
108+
if (!response.ok) {
109+
const message = await getMessageFromResponse(response);
110+
111+
return Promise.reject(
112+
new Error(
113+
`fetch API response status: ${response.status}${
114+
message ? `\n message is \`${message}\`` : ''
115+
}`
116+
)
117+
);
118+
}
119+
120+
if (method === 'DELETE') return;
121+
122+
return response.json();
123+
} catch (error) {
124+
if (error.data) {
125+
throw error.data;
126+
}
127+
128+
if (error.response?.data) {
129+
throw error.response.data;
130+
}
131+
132+
return Promise.reject(
133+
new Error(`Network Error.\n Details: ${error}`)
134+
);
135+
}
136+
},
137+
{
138+
retries: retryOption ? MAX_RETRY_COUNT : 0,
139+
onRetry: (err, num) => {
140+
console.log(err);
141+
console.log(`Waiting for retry (${num}/${MAX_RETRY_COUNT})`);
142+
},
143+
minTimeout: MIN_TIMEOUT_MS,
144+
}
145+
);
105146
};
106147

107148
/**

src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export interface MicroCMSClient {
77
serviceDomain: string;
88
apiKey: string;
99
customFetch?: Fetch;
10+
retry?: boolean;
1011
}
1112

1213
type depthNumber = 1 | 2 | 3;
@@ -26,7 +27,7 @@ export interface MicroCMSQueries {
2627
depth?: depthNumber;
2728
ids?: string | string[];
2829
filters?: string;
29-
richEditorFormat?: 'html'|'object';
30+
richEditorFormat?: 'html' | 'object';
3031
}
3132

3233
/**

src/utils/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
export const BASE_DOMAIN = 'microcms.io';
22
export const API_VERSION = 'v1';
3+
export const MAX_RETRY_COUNT = 2;
4+
export const MIN_TIMEOUT_MS = 5000;

0 commit comments

Comments
 (0)