Skip to content

Commit c7e9c70

Browse files
Converting backoff.js to TypeScript (#328)
1 parent a323831 commit c7e9c70

1 file changed

Lines changed: 72 additions & 82 deletions

File tree

Lines changed: 72 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const DEFAULT_JITTER_FACTOR = 1.0;
5353
/*!
5454
* The timeout handler used by `ExponentialBackoff`.
5555
*/
56-
let delayExecution = setTimeout;
56+
let delayExecution: typeof setTimeout = setTimeout;
5757

5858
/**
5959
* Allows overriding of the timeout handler used by the exponential backoff
@@ -64,10 +64,31 @@ let delayExecution = setTimeout;
6464
* @private
6565
* @param {function} handler A handler than matches the API of `setTimeout()`.
6666
*/
67-
export function setTimeoutHandler(handler) {
67+
export function setTimeoutHandler(handler: typeof setTimeout): void {
6868
delayExecution = handler;
6969
}
7070

71+
/**
72+
* Configuration object to adjust the delays of the exponential backoff
73+
* algorithm.
74+
*
75+
* @private
76+
*/
77+
export interface ExponentialBackoffOptions {
78+
/** Optional override for the initial retry delay. */
79+
initialDelayMs?: number;
80+
/** Optional override for the exponential backoff factor. */
81+
backoffFactor?: number;
82+
/** Optional override for the maximum retry delay. */
83+
maxDelayMs?: number;
84+
/**
85+
* Optional override to control the itter factor by which to randomize
86+
* attempts (0 means no randomization, 1.0 means +/-50% randomization). It is
87+
* suggested not to exceed this range.
88+
*/
89+
jitterFactor?: number;
90+
}
91+
7192
/**
7293
* A helper for running delayed tasks following an exponential backoff curve
7394
* between attempts.
@@ -81,70 +102,49 @@ export function setTimeoutHandler(handler) {
81102
*/
82103
export class ExponentialBackoff {
83104
/**
84-
* @param {number=} options.initialDelayMs Optional override for the initial
85-
* retry delay.
86-
* @param {number=} options.backoffFactor Optional override for the
87-
* exponential backoff factor.
88-
* @param {number=} options.maxDelayMs Optional override for the maximum
89-
* retry delay.
90-
* @param {number=} options.jitterFactor Optional override to control the
91-
* jitter factor by which to randomize attempts (0 means no randomization,
92-
* 1.0 means +/-50% randomization). It is suggested not to exceed this range.
105+
* The initial delay (used as the base delay on the first retry attempt).
106+
* Note that jitter will still be applied, so the actual delay could be as
107+
* little as 0.5*initialDelayMs (based on a jitter factor of 1.0).
108+
*/
109+
private readonly initialDelayMs: number;
110+
111+
/**
112+
* The multiplier to use to determine the extended base delay after each
113+
* attempt.
114+
*/
115+
private readonly backoffFactor: number;
116+
117+
/**
118+
* The maximum base delay after which no further backoff is performed.
119+
* Note that jitter will still be applied, so the actual delay could be as
120+
* much as 1.5*maxDelayMs (based on a jitter factor of 1.0).
121+
*/
122+
private readonly maxDelayMs: number;
123+
124+
/**
125+
* The jitter factor that controls the random distribution of the backoff
126+
* points.
127+
*/
128+
private readonly jitterFactor: number;
129+
130+
/**
131+
* The backoff delay of the current attempt.
93132
*/
94-
constructor(options) {
95-
options = options || {};
96-
97-
/**
98-
* The initial delay (used as the base delay on the first retry attempt).
99-
* Note that jitter will still be applied, so the actual delay could be as
100-
* little as 0.5*initialDelayMs (based on a jitter factor of 1.0).
101-
*
102-
* @type {number}
103-
* @private
104-
*/
105-
this._initialDelayMs = options.initialDelayMs !== undefined ?
133+
private currentBaseMs = 0;
134+
135+
constructor(options: ExponentialBackoffOptions = {}) {
136+
this.initialDelayMs = options.initialDelayMs !== undefined ?
106137
options.initialDelayMs :
107138
DEFAULT_BACKOFF_INITIAL_DELAY_MS;
108-
109-
/**
110-
* The multiplier to use to determine the extended base delay after each
111-
* attempt.
112-
* @type {number}
113-
* @private
114-
*/
115-
this._backoffFactor = options.backoffFactor !== undefined ?
139+
this.backoffFactor = options.backoffFactor !== undefined ?
116140
options.backoffFactor :
117141
DEFAULT_BACKOFF_FACTOR;
118-
119-
/**
120-
* The maximum base delay after which no further backoff is performed.
121-
* Note that jitter will still be applied, so the actual delay could be as
122-
* much as 1.5*maxDelayMs (based on a jitter factor of 1.0).
123-
*
124-
* @type {number}
125-
* @private
126-
*/
127-
this._maxDelayMs = options.maxDelayMs !== undefined ?
142+
this.maxDelayMs = options.maxDelayMs !== undefined ?
128143
options.maxDelayMs :
129144
DEFAULT_BACKOFF_MAX_DELAY_MS;
130-
131-
/**
132-
* The jitter factor that controls the random distribution of the backoff
133-
* points.
134-
*
135-
* @type {number}
136-
* @private
137-
*/
138-
this._jitterFactor = options.jitterFactor !== undefined ?
145+
this.jitterFactor = options.jitterFactor !== undefined ?
139146
options.jitterFactor :
140147
DEFAULT_JITTER_FACTOR;
141-
142-
/**
143-
* The backoff delay of the current attempt.
144-
* @type {number}
145-
* @private
146-
*/
147-
this._currentBaseMs = 0;
148148
}
149149

150150
/**
@@ -153,51 +153,41 @@ export class ExponentialBackoff {
153153
* The very next backoffAndWait() will have no delay. If it is called again
154154
* (i.e. due to an error), initialDelayMs (plus jitter) will be used, and
155155
* subsequent ones will increase according to the backoffFactor.
156-
*
157-
* @private
158156
*/
159-
reset() {
160-
this._currentBaseMs = 0;
157+
private reset(): void {
158+
this.currentBaseMs = 0;
161159
}
162160

163161
/**
164162
* Resets the backoff delay to the maximum delay (e.g. for use after a
165163
* RESOURCE_EXHAUSTED error).
166-
*
167-
* @private
168164
*/
169-
resetToMax() {
170-
this._currentBaseMs = this._maxDelayMs;
165+
private resetToMax(): void {
166+
this.currentBaseMs = this.maxDelayMs;
171167
}
172168

173169
/**
174170
* Returns a promise that resolves after currentDelayMs, and increases the
175171
* delay for any subsequent attempts.
176172
*
177-
* @private
178-
* @return {Promise.<void>} A Promise that resolves when the current delay
179-
* elapsed.
173+
* @return A Promise that resolves when the current delay elapsed.
180174
*/
181-
backoffAndWait() {
175+
backoffAndWait(): Promise<void> {
182176
// First schedule using the current base (which may be 0 and should be
183177
// honored as such).
184-
const delayWithJitterMs = this._currentBaseMs + this._jitterDelayMs();
185-
if (this._currentBaseMs > 0) {
178+
const delayWithJitterMs = this.currentBaseMs + this.jitterDelayMs();
179+
if (this.currentBaseMs > 0) {
186180
logger(
187-
'ExponentialBackoff.backoffAndWait',
181+
'ExponentialBackoff.backoffAndWait', null,
188182
`Backing off for ${delayWithJitterMs} ms ` +
189-
`(base delay: ${this._currentBaseMs} ms)`);
183+
`(base delay: ${this.currentBaseMs} ms)`);
190184
}
191185

192186
// Apply backoff factor to determine next delay and ensure it is within
193187
// bounds.
194-
this._currentBaseMs *= this._backoffFactor;
195-
if (this._currentBaseMs < this._initialDelayMs) {
196-
this._currentBaseMs = this._initialDelayMs;
197-
}
198-
if (this._currentBaseMs > this._maxDelayMs) {
199-
this._currentBaseMs = this._maxDelayMs;
200-
}
188+
this.currentBaseMs *= this.backoffFactor;
189+
this.currentBaseMs = Math.max(this.currentBaseMs, this.initialDelayMs);
190+
this.currentBaseMs = Math.min(this.currentBaseMs, this.maxDelayMs);
201191

202192
return new Promise(resolve => {
203193
delayExecution(resolve, delayWithJitterMs);
@@ -211,7 +201,7 @@ export class ExponentialBackoff {
211201
* @private
212202
* @returns {number} The jitter to apply based on the current delay.
213203
*/
214-
_jitterDelayMs() {
215-
return (Math.random() - 0.5) * this._jitterFactor * this._currentBaseMs;
204+
private jitterDelayMs() {
205+
return (Math.random() - 0.5) * this.jitterFactor * this.currentBaseMs;
216206
}
217207
}

0 commit comments

Comments
 (0)