Skip to content

Commit f4e3a29

Browse files
Ryrdensinedied
authored andcommitted
feat: throttled Put request and add retry + delay
1 parent 3bd6e32 commit f4e3a29

File tree

1 file changed

+43
-16
lines changed

1 file changed

+43
-16
lines changed

src/api.ts

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,35 @@ import { type Article, type RemoteArticleData, type ArticleStats } from './model
77
const debug = Debug('devto');
88
const apiUrl = 'https://dev.to/api';
99
const paginationLimit = 1000;
10+
const maxRetries = 3;
11+
const retryDelay = 1000; // 1 second delay before retrying
1012

1113
// There's a limit of 10 articles created each 30 seconds by the same user,
1214
// so we need to throttle the API calls in that case.
1315
// The insane type casting is due to typing issues with p-throttle.
1416
const throttledPostForCreate = pThrottle({ limit: 10, interval: 30_500 })(got.post) as any as Got['post'];
1517

18+
// There's a limit of 30 requests each 30 seconds by the same user, so we need to throttle the API calls in that case too.
19+
const throttledPutForUpdate = pThrottle({ limit: 30, interval: 30_500 })(
20+
async (url: string, options: any) => got.put(url, options)
21+
) as any as Got['put'];
22+
23+
async function delay(ms: number): Promise<void> {
24+
return new Promise(resolve => setTimeout(resolve, ms));
25+
}
26+
27+
async function retryRequest(fn: () => Promise<RemoteArticleData>, retries: number): Promise<RemoteArticleData> {
28+
try {
29+
return await fn();
30+
} catch (error) {
31+
if (retries === 0 || !(error instanceof RequestError && error.response?.statusCode === 429)) {
32+
throw error;
33+
}
34+
await delay(retryDelay);
35+
return retryRequest(fn, retries - 1);
36+
}
37+
}
38+
1639
export async function getAllArticles(devtoKey: string): Promise<RemoteArticleData[]> {
1740
try {
1841
const articles = [];
@@ -69,22 +92,26 @@ export async function getLastArticlesStats(devtoKey: string, number: number): Pr
6992
}
7093

7194
export async function updateRemoteArticle(article: Article, devtoKey: string): Promise<RemoteArticleData> {
72-
try {
73-
const markdown = matter.stringify(article, article.data, { lineWidth: -1 } as any);
74-
const { id } = article.data;
75-
// Throttle API calls in case of article creation
76-
const get = id ? got.put : throttledPostForCreate;
77-
const result = await get(`${apiUrl}/articles${id ? `/${id}` : ''}`, {
78-
headers: { 'api-key': devtoKey },
79-
json: { article: { title: article.data.title, body_markdown: markdown } },
80-
responseType: 'json'
81-
});
82-
return result.body as RemoteArticleData;
83-
} catch (error) {
84-
if (error instanceof RequestError && error.response) {
85-
debug('Debug infos: %O', error.response.body);
95+
const update = async (): Promise<RemoteArticleData> => {
96+
try {
97+
const markdown = matter.stringify(article, article.data, { lineWidth: -1 } as any);
98+
const { id } = article.data;
99+
// Throttle API calls in case of article creation
100+
const get = id ? throttledPutForUpdate : throttledPostForCreate;
101+
const result = await get(`${apiUrl}/articles${id ? `/${id}` : ''}`, {
102+
headers: { 'api-key': devtoKey },
103+
json: { article: { title: article.data.title, body_markdown: markdown } },
104+
responseType: 'json'
105+
});
106+
return result.body as RemoteArticleData;
107+
} catch (error) {
108+
if (error instanceof RequestError && error.response) {
109+
debug('Debug infos: %O', error.response.body);
110+
}
111+
112+
throw error;
86113
}
114+
};
87115

88-
throw error;
89-
}
116+
return retryRequest(update, maxRetries);
90117
}

0 commit comments

Comments
 (0)