Skip to content

Commit 2dd5edb

Browse files
committed
Add retry logic with exponential backoff to API client
1 parent 2a8db2a commit 2dd5edb

File tree

1 file changed

+39
-3
lines changed

1 file changed

+39
-3
lines changed

nodejs/src/index.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ export class API {
2828
baseURL: hackmdAPIEndpointURL,
2929
headers:{
3030
"Content-Type": "application/json",
31-
}
31+
},
32+
timeout: 30000, // Increased timeout for low bandwidth
3233
})
3334

3435
this.axios.interceptors.request.use(
@@ -71,13 +72,48 @@ export class API {
7172
`Received an error response (${err.response.status} ${err.response.statusText}) from HackMD`,
7273
err.response.status,
7374
err.response.statusText,
74-
)
75+
);
7576
}
7677
}
77-
)
78+
);
7879
}
80+
this.createRetryInterceptor(this.axios, 3); // Add retry interceptor with maxRetries = 3
81+
}
82+
83+
// Utility functions for exponential backoff and retry logic
84+
private exponentialBackoff(retries: number): number {
85+
return Math.pow(2, retries) * 100; // Exponential backoff with base delay of 100ms
86+
}
87+
88+
private isRetryableError(error: AxiosError): boolean {
89+
// Retry on network errors, 5xx errors, and rate limiting (429)
90+
return (
91+
!error.response ||
92+
(error.response.status >= 500 && error.response.status < 600) ||
93+
error.response.status === 429
94+
);
7995
}
8096

97+
// Create retry interceptor function
98+
private createRetryInterceptor(axiosInstance: AxiosInstance, maxRetries: number): void {
99+
let retryCount = 0;
100+
101+
axiosInstance.interceptors.response.use(
102+
response => response,
103+
async error => {
104+
if (retryCount < maxRetries && this.isRetryableError(error)) {
105+
retryCount++;
106+
const delay = this.exponentialBackoff(retryCount);
107+
console.warn(`Retrying request... attempt #${retryCount} after delay of ${delay}ms`);
108+
await new Promise(resolve => setTimeout(resolve, delay));
109+
return axiosInstance(error.config);
110+
}
111+
112+
retryCount = 0; // Reset retry count after a successful request
113+
return Promise.reject(error);
114+
}
115+
);
116+
}
81117
async getMe<Opt extends RequestOptions> (options = defaultOption as Opt): Promise<OptionReturnType<Opt, GetMe>> {
82118
return this.unwrapData(this.axios.get<GetMe>("me"), options.unwrapData) as unknown as OptionReturnType<Opt, GetMe>
83119
}

0 commit comments

Comments
 (0)