Skip to content

Commit

Permalink
feat: throttled Put request and add retry + delay
Browse files Browse the repository at this point in the history
  • Loading branch information
Ryrden authored and sinedied committed Jul 18, 2024
1 parent 3bd6e32 commit f4e3a29
Showing 1 changed file with 43 additions and 16 deletions.
59 changes: 43 additions & 16 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,35 @@ import { type Article, type RemoteArticleData, type ArticleStats } from './model
const debug = Debug('devto');
const apiUrl = 'https://dev.to/api';
const paginationLimit = 1000;
const maxRetries = 3;
const retryDelay = 1000; // 1 second delay before retrying

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

// 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.
const throttledPutForUpdate = pThrottle({ limit: 30, interval: 30_500 })(
async (url: string, options: any) => got.put(url, options)
) as any as Got['put'];

async function delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}

async function retryRequest(fn: () => Promise<RemoteArticleData>, retries: number): Promise<RemoteArticleData> {
try {
return await fn();
} catch (error) {
if (retries === 0 || !(error instanceof RequestError && error.response?.statusCode === 429)) {
throw error;
}
await delay(retryDelay);
return retryRequest(fn, retries - 1);
}
}

export async function getAllArticles(devtoKey: string): Promise<RemoteArticleData[]> {
try {
const articles = [];
Expand Down Expand Up @@ -69,22 +92,26 @@ export async function getLastArticlesStats(devtoKey: string, number: number): Pr
}

export async function updateRemoteArticle(article: Article, devtoKey: string): Promise<RemoteArticleData> {
try {
const markdown = matter.stringify(article, article.data, { lineWidth: -1 } as any);
const { id } = article.data;
// Throttle API calls in case of article creation
const get = id ? got.put : throttledPostForCreate;
const result = await get(`${apiUrl}/articles${id ? `/${id}` : ''}`, {
headers: { 'api-key': devtoKey },
json: { article: { title: article.data.title, body_markdown: markdown } },
responseType: 'json'
});
return result.body as RemoteArticleData;
} catch (error) {
if (error instanceof RequestError && error.response) {
debug('Debug infos: %O', error.response.body);
const update = async (): Promise<RemoteArticleData> => {
try {
const markdown = matter.stringify(article, article.data, { lineWidth: -1 } as any);
const { id } = article.data;
// Throttle API calls in case of article creation
const get = id ? throttledPutForUpdate : throttledPostForCreate;
const result = await get(`${apiUrl}/articles${id ? `/${id}` : ''}`, {
headers: { 'api-key': devtoKey },
json: { article: { title: article.data.title, body_markdown: markdown } },
responseType: 'json'
});
return result.body as RemoteArticleData;
} catch (error) {
if (error instanceof RequestError && error.response) {
debug('Debug infos: %O', error.response.body);
}

throw error;
}
};

throw error;
}
return retryRequest(update, maxRetries);
}

0 comments on commit f4e3a29

Please sign in to comment.