@@ -37,14 +37,47 @@ export interface BackoffOptions {
37
37
}
38
38
39
39
export class BackoffTimeout {
40
- private initialDelay : number = INITIAL_BACKOFF_MS ;
41
- private multiplier : number = BACKOFF_MULTIPLIER ;
42
- private maxDelay : number = MAX_BACKOFF_MS ;
43
- private jitter : number = BACKOFF_JITTER ;
40
+ /**
41
+ * The delay time at the start, and after each reset.
42
+ */
43
+ private readonly initialDelay : number = INITIAL_BACKOFF_MS ;
44
+ /**
45
+ * The exponential backoff multiplier.
46
+ */
47
+ private readonly multiplier : number = BACKOFF_MULTIPLIER ;
48
+ /**
49
+ * The maximum delay time
50
+ */
51
+ private readonly maxDelay : number = MAX_BACKOFF_MS ;
52
+ /**
53
+ * The maximum fraction by which the delay time can randomly vary after
54
+ * applying the multiplier.
55
+ */
56
+ private readonly jitter : number = BACKOFF_JITTER ;
57
+ /**
58
+ * The delay time for the next time the timer runs.
59
+ */
44
60
private nextDelay : number ;
61
+ /**
62
+ * The handle of the underlying timer. If running is false, this value refers
63
+ * to an object representing a timer that has ended, but it can still be
64
+ * interacted with without error.
65
+ */
45
66
private timerId : NodeJS . Timer ;
67
+ /**
68
+ * Indicates whether the timer is currently running.
69
+ */
46
70
private running = false ;
71
+ /**
72
+ * Indicates whether the timer should keep the Node process running if no
73
+ * other async operation is doing so.
74
+ */
47
75
private hasRef = true ;
76
+ /**
77
+ * The time that the currently running timer was started. Only valid if
78
+ * running is true.
79
+ */
80
+ private startTime : Date = new Date ( ) ;
48
81
49
82
constructor ( private callback : ( ) => void , options ?: BackoffOptions ) {
50
83
if ( options ) {
@@ -66,18 +99,23 @@ export class BackoffTimeout {
66
99
clearTimeout ( this . timerId ) ;
67
100
}
68
101
69
- /**
70
- * Call the callback after the current amount of delay time
71
- */
72
- runOnce ( ) {
73
- this . running = true ;
102
+ private runTimer ( delay : number ) {
74
103
this . timerId = setTimeout ( ( ) => {
75
104
this . callback ( ) ;
76
105
this . running = false ;
77
- } , this . nextDelay ) ;
106
+ } , delay ) ;
78
107
if ( ! this . hasRef ) {
79
108
this . timerId . unref ?.( ) ;
80
109
}
110
+ }
111
+
112
+ /**
113
+ * Call the callback after the current amount of delay time
114
+ */
115
+ runOnce ( ) {
116
+ this . running = true ;
117
+ this . startTime = new Date ( ) ;
118
+ this . runTimer ( this . nextDelay ) ;
81
119
const nextBackoff = Math . min (
82
120
this . nextDelay * this . multiplier ,
83
121
this . maxDelay
@@ -97,21 +135,44 @@ export class BackoffTimeout {
97
135
}
98
136
99
137
/**
100
- * Reset the delay time to its initial value.
138
+ * Reset the delay time to its initial value. If the timer is still running,
139
+ * retroactively apply that reset to the current timer.
101
140
*/
102
141
reset ( ) {
103
142
this . nextDelay = this . initialDelay ;
143
+ if ( this . running ) {
144
+ const now = new Date ( ) ;
145
+ const newEndTime = this . startTime ;
146
+ newEndTime . setMilliseconds ( newEndTime . getMilliseconds ( ) + this . nextDelay ) ;
147
+ clearTimeout ( this . timerId ) ;
148
+ if ( now < newEndTime ) {
149
+ this . runTimer ( newEndTime . getTime ( ) - now . getTime ( ) ) ;
150
+ } else {
151
+ this . running = false ;
152
+ }
153
+ }
104
154
}
105
155
156
+ /**
157
+ * Check whether the timer is currently running.
158
+ */
106
159
isRunning ( ) {
107
160
return this . running ;
108
161
}
109
162
163
+ /**
164
+ * Set that while the timer is running, it should keep the Node process
165
+ * running.
166
+ */
110
167
ref ( ) {
111
168
this . hasRef = true ;
112
169
this . timerId . ref ?.( ) ;
113
170
}
114
171
172
+ /**
173
+ * Set that while the timer is running, it should not keep the Node process
174
+ * running.
175
+ */
115
176
unref ( ) {
116
177
this . hasRef = false ;
117
178
this . timerId . unref ?.( ) ;
0 commit comments