-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathretryableHttpClient.ts
138 lines (121 loc) · 4.13 KB
/
retryableHttpClient.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
// Copyright 2020 Cognite AS
import isFunction from 'lodash/isFunction';
import { sleepPromise } from '../utils';
import {
BasicHttpClient,
type HttpRequest,
type HttpRequestOptions,
type HttpResponse,
} from './basicHttpClient';
import { MAX_RETRY_ATTEMPTS, type RetryValidator } from './retryValidator';
/**
* The `RetryableHttpClient` class extends the functionality of a basic HTTP client
* by adding automatic retry logic for failed requests. This is particularly useful
* for improving the resilience of applications that depend on unreliable network
* connections or external services that may experience intermittent failures.
*
* This class includes methods for:
* - Configuring retry policies, including the number of retries and delay between retries.
* - Making HTTP requests with automatic retries on failure.
* - Handling different types of HTTP responses and errors.
*
* The `RetryableHttpClient` is designed to be used in scenarios where you need to
* make HTTP requests that may occasionally fail and need to be retried. It ensures
* that transient errors do not cause the application to fail, improving overall
* reliability and user experience.
*
* @remarks
* This class builds on top of a basic HTTP client and adds retry logic. It uses
* custom error handling and response parsing methods to ensure that retries are
* only attempted for transient errors and not for permanent failures.
*
* @see {@link BasicHttpClient}
* @see {@link HttpError}
* @see {@link HttpHeaders}
* @see {@link HttpResponseType}
*/
export class RetryableHttpClient extends BasicHttpClient {
private static calculateRetryDelayInMs(retryCount: number) {
const INITIAL_RETRY_DELAY_IN_MS = 250;
return INITIAL_RETRY_DELAY_IN_MS + ((2 ** retryCount - 1) / 2) * 1000;
}
constructor(
baseUrl: string,
private retryValidator: RetryValidator
) {
super(baseUrl);
}
public get<ResponseType>(
path: string,
options: RetryableHttpRequestOptions = {}
) {
return super.get<ResponseType>(path, options);
}
public post<ResponseType>(
path: string,
options: RetryableHttpRequestOptions = {}
) {
return super.post<ResponseType>(path, options);
}
public put<ResponseType>(
path: string,
options: RetryableHttpRequestOptions = {}
) {
return super.put<ResponseType>(path, options);
}
public delete<ResponseType>(
path: string,
options: RetryableHttpRequestOptions = {}
) {
return super.delete<ResponseType>(path, options);
}
public patch<ResponseType>(
path: string,
options: RetryableHttpRequestOptions = {}
) {
return super.patch<ResponseType>(path, options);
}
protected async preRequest(
request: RetryableHttpRequest
): Promise<RetryableHttpRequest> {
return super.preRequest(request);
}
protected async postRequest<T>(
response: HttpResponse<T>,
request: RetryableHttpRequest,
mutatedRequest: RetryableHttpRequest
): Promise<HttpResponse<T>> {
return super.postRequest<T>(response, request, mutatedRequest);
}
protected async rawRequest<ResponseType>(
request: RetryableHttpRequest
): Promise<HttpResponse<ResponseType>> {
let retryCount = 0;
while (true) {
const response = await super.rawRequest<ResponseType>(request);
const retryValidator = isFunction(request.retryValidator)
? request.retryValidator
: this.retryValidator;
const shouldRetry =
retryCount < MAX_RETRY_ATTEMPTS &&
request.retryValidator !== false &&
retryValidator(request, response, retryCount);
if (!shouldRetry) {
return response;
}
const delayInMs = RetryableHttpClient.calculateRetryDelayInMs(retryCount);
await sleepPromise(delayInMs);
retryCount++;
}
}
protected async request<ResponseType>(request: RetryableHttpRequest) {
return super.request<ResponseType>(request);
}
}
export type RetryableHttpRequest = HttpRequest &
HttpRequestRetryValidatorOptions;
export type RetryableHttpRequestOptions = HttpRequestOptions &
HttpRequestRetryValidatorOptions;
type HttpRequestRetryValidatorOptions = {
retryValidator?: false | RetryValidator;
};