@creejs/commons-retrier 1.0.8 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/retrier.js DELETED
@@ -1,530 +0,0 @@
1
- // internal
2
- import { TypeAssert, TypeUtils, PromiseUtils } from '@creejs/commons-lang'
3
- import { EventEmitter } from '@creejs/commons-events'
4
-
5
- // owned
6
- // eslint-disable-next-line no-unused-vars
7
- import Policy from './policy.js'
8
- import Event from './event.js'
9
- import FixedIntervalPolicy from './policy/fixed-interval-policy.js'
10
- import FixedIncreasePolicy from './policy/fixed-increase-policy.js'
11
- import FactoreIncreasePolicy from './policy/factor-increase-policy.js'
12
- import ShuttlePolicy from './policy/shuttle-policy.js'
13
- import Task from './task.js'
14
- import AlwaysTask from './alway-task.js'
15
- import { DefaultMaxRetries } from './constants.js'
16
-
17
- // module vars
18
- const { assertPositive, assertString, assertFunction, assertNumber } = TypeAssert
19
- const { isNil } = TypeUtils
20
- const TaskTimoutFlag = '!#@%$&^*'
21
-
22
- /**
23
- * @extends EventEmitter
24
- */
25
- export default class Retrier {
26
- /**
27
- * Creates a new Retrier instance with a fixed interval policy.
28
- * @param {number} [fixedInterval=1000] - The fixed interval in milliseconds between retry attempts. Defaults to 1000ms if not provided.
29
- */
30
- constructor (fixedInterval) {
31
- EventEmitter.mixin(this)
32
- /**
33
- * @type {Policy}
34
- */
35
- this._policy = new FixedIntervalPolicy(fixedInterval ?? 1000)
36
- this._maxRetries = DefaultMaxRetries
37
- this._currentRetries = 1
38
- /**
39
- * Timetou for total operation
40
- * @type {number}
41
- */
42
- this._timeout = 120000 // 120s
43
- /**
44
- * Timetou for single task
45
- */
46
- this._taskTimeout = 2000 // 20s
47
- this._name = 'unamed' // Retrier name
48
-
49
- /**
50
- * A Deferred Object as Singal to prevent Task concurrent start
51
- * @type {{resolve:Function, reject:Function, promise: Promise<*>}|undefined}
52
- */
53
- this._taskingFlag = undefined
54
-
55
- /**
56
- * A Deferred Object as Singal to prevent Task concurrent stop
57
- * @type {{resolve:Function, reject:Function, promise: Promise<*>}|undefined}
58
- */
59
- this._breakFlag = undefined
60
- /**
61
- * Reason for break
62
- * @type {Error|undefined}
63
- */
64
- this._breakReason = undefined
65
- }
66
-
67
- get running () {
68
- return !isNil(this._taskingFlag)
69
- }
70
-
71
- /**
72
- * Sets the name of the retrier.
73
- * @param {string} retrierName - The name to assign to the retrier.
74
- * @returns {this} The retrier instance for chaining.
75
- */
76
- name (retrierName) {
77
- assertString(retrierName, 'retrierName')
78
- this._name = retrierName
79
- return this
80
- }
81
-
82
- /**
83
- * Sets the retry attempts to be infinite by setting max retries to maximum safe integer.
84
- * @returns {Object} The retrier instance for chaining.
85
- */
86
- infinite () {
87
- this._maxRetries = Infinity
88
- return this
89
- }
90
-
91
- /**
92
- * Sets the maximum number of retry attempts.
93
- * @param {number} times - The maximum number of retries.
94
- * @returns {this} The Retrier instance for chaining.
95
- */
96
- times (times) {
97
- return this.maxRetries(times)
98
- }
99
-
100
- /**
101
- * Sets the maximum number of retry attempts.
102
- * @param {number} maxRetries - The maximum number of retries (must be positive).
103
- * @returns {this} The retrier instance for chaining.
104
- */
105
- maxRetries (maxRetries) {
106
- assertPositive(maxRetries, 'maxRetries')
107
- this._maxRetries = maxRetries
108
- return this
109
- }
110
-
111
- /**
112
- * Sets the minimum retry delay in milliseconds.
113
- * @param {number} min - The minimum delay (must be positive and less than max).
114
- * @returns {this} The retrier instance for chaining.
115
- * @throws {Error} If min is not positive or is greater than/equal to max.
116
- */
117
- min (min) {
118
- this._policy.min(min)
119
- return this
120
- }
121
-
122
- /**
123
- * Sets the maximum retry retry delay in milliseconds.
124
- * @param {number} max - The maximum delay (must be positive and greater than min).
125
- * @throws {Error} If max is not greater than min.
126
- * @returns {this} The retrier instance for chaining.
127
- */
128
- max (max) {
129
- this._policy.max(max)
130
- return this
131
- }
132
-
133
- /**
134
- * Sets the minimum and maximum intervals for retries.
135
- * @param {number} min - Minimum delay in milliseconds (must be positive and less than max)
136
- * @param {number} max - Maximum delay in milliseconds (must be positive and greater than min)
137
- * @returns {Retrier} Returns the Retrier instance for chaining
138
- * @throws {Error} If min is not less than max or if values are not positive
139
- */
140
- range (min, max) {
141
- this._policy.range(min, max)
142
- return this
143
- }
144
-
145
- /**
146
- * Sets a fixed interval retry policy.
147
- * @param {number} fixedInterval - The fixed interval in milliseconds between retries.
148
- * @returns {Retrier} The Retrier instance for chaining.
149
- */
150
- fixedInterval (fixedInterval) {
151
- const oldPolicy = this._policy
152
- if (oldPolicy instanceof FixedIntervalPolicy) {
153
- oldPolicy.interval = fixedInterval
154
- return this
155
- }
156
- const newPolicy = new FixedIntervalPolicy(fixedInterval)
157
- oldPolicy?.copyPolicySetting(newPolicy)
158
- newPolicy.reset()
159
- this._policy = newPolicy
160
- return this
161
- }
162
-
163
- /**
164
- * Sets a fixed increase policy for retry intervals.
165
- * @param {number} increasement - The fixed amount to increase the interval by on each retry.
166
- * @returns {this} The retrier instance for chaining.
167
- */
168
- fixedIncrease (increasement) {
169
- const oldPolicy = this._policy
170
- if (oldPolicy instanceof FixedIncreasePolicy) {
171
- oldPolicy.increasement = increasement
172
- return this
173
- }
174
- const newPolicy = new FixedIncreasePolicy(increasement)
175
- oldPolicy?.copyPolicySetting(newPolicy)
176
- newPolicy.reset()
177
- this._policy = newPolicy
178
- return this
179
- }
180
-
181
- /**
182
- * Sets a fixed increase factor for retry delays.
183
- * @param {number} factor - The multiplier for delay increase between retries.
184
- * @returns {this} The retrier instance for method chaining.
185
- */
186
- factorIncrease (factor) {
187
- const oldPolicy = this._policy
188
- if (oldPolicy instanceof FactoreIncreasePolicy) {
189
- oldPolicy.factor = factor
190
- return this
191
- }
192
- const newPolicy = new FactoreIncreasePolicy(factor)
193
- oldPolicy?.copyPolicySetting(newPolicy)
194
- newPolicy.reset()
195
- this._policy = newPolicy
196
- return this
197
- }
198
-
199
- /**
200
- * Sets a shuttle retry policy with the given step length.
201
- * @param {number} stepLength - The interval between retry attempts.
202
- * @returns {this} The Retrier instance for chaining.
203
- */
204
- shuttleInterval (stepLength) {
205
- const oldPolicy = this._policy
206
- if (oldPolicy instanceof ShuttlePolicy) {
207
- oldPolicy.stepLength = stepLength
208
- return this
209
- }
210
- const newPolicy = new ShuttlePolicy(stepLength)
211
- oldPolicy?.copyPolicySetting(newPolicy)
212
- newPolicy.reset()
213
- this._policy = newPolicy
214
- return this
215
- }
216
-
217
- /**
218
- * Sets the timeout duration for each Task execution.
219
- * 1. must > 0
220
- * @param {number} timeout - The timeout duration in milliseconds.
221
- * @returns {Object} The retrier instance for chaining.
222
- */
223
- taskTimeout (timeout) {
224
- assertPositive(timeout, 'timeout')
225
- this._taskTimeout = timeout
226
- return this
227
- }
228
-
229
- /**
230
- * Sets the timeout duration for all retries.
231
- * 1. <= 0 - no timeout
232
- * 2. \> 0 - timeout duration in milliseconds
233
- * @param {number} timeout - The timeout duration in milliseconds.
234
- * @returns {Object} The retrier instance for chaining.
235
- */
236
- timeout (timeout) {
237
- assertNumber(timeout, 'timeout')
238
- this._timeout = timeout
239
- return this
240
- }
241
-
242
- /**
243
- * Sets the task function to be retried.
244
- * @param {Function} task - The function to be executed and retried on failure.
245
- * @returns {this} Returns the retrier instance for chaining.
246
- */
247
- task (task) {
248
- assertFunction(task, 'task')
249
- this._task = new Task(this, task)
250
- return this
251
- }
252
-
253
- /**
254
- * alias of {@linkcode Retrier.task()}
255
- * @param {Function} task - The function to be executed and retried
256
- * @return {this}
257
- */
258
- retry (task) {
259
- this.task(task)
260
- return this
261
- }
262
-
263
- /**
264
- * Executes the given task, and never stop
265
- * 1. if the task fails, will retry it after the interval generated by RetryPolicy
266
- * 2. if the task succeeds, reset RetryPolicy to Minimum Interval and continue to run the task
267
- * @param {Function} task - The async function to execute and retry.
268
- * @param {boolean} [resetAfterSuccess=false] - Whether to reset retry counters after success.
269
- * @returns {this} The Retrier instance for chaining.
270
- */
271
- always (task, resetAfterSuccess = false) {
272
- this._task = new AlwaysTask(this, task, resetAfterSuccess)
273
- return this
274
- }
275
-
276
- /**
277
- * Starts the retry process.
278
- * @returns {Promise<*>}
279
- */
280
- async start () {
281
- if (this._task == null) {
282
- throw new Error('No Task to Retry')
283
- }
284
- if (this._taskingFlag != null) {
285
- return this._taskingFlag.promise
286
- }
287
- const startAt = Date.now()
288
- let lastError = null
289
- // @ts-ignore
290
- this.emit(Event.Start, startAt)
291
- this._taskingFlag = PromiseUtils.defer()
292
- let latency = null
293
- while (true) {
294
- // need to stop?
295
- if (this._breakFlag != null) {
296
- this._taskingFlag.reject(this._breakReason)
297
- break
298
- }
299
-
300
- latency = Date.now() - startAt
301
-
302
- // total timeout?
303
- if (!isInfinite(this._timeout) && latency >= this._timeout) { // total timeout
304
- // @ts-ignore
305
- this.emit(Event.Timeout, this._currentRetries, latency, this._timeout)
306
- // always task, treat as success, resolve the whole promise with <void>
307
- if (AlwaysTask.isAlwaysTask(this._task)) {
308
- this._taskingFlag.resolve()
309
- break
310
- }
311
- this._taskingFlag.reject(lastError ?? new Error(`Timeout "${this._timeout}" Exceeded`))
312
- break
313
- }
314
-
315
- // @ts-ignore
316
- this.emit(Event.Retry, this._currentRetries, latency)
317
- const task = this._task // take task, it may be changed in events' callback functions
318
- const nextDelay = this._policy.generate()
319
- try {
320
- try {
321
- await PromiseUtils.timeout(task.execute(this._currentRetries, latency, nextDelay), this._taskTimeout, TaskTimoutFlag)
322
- } catch (err) {
323
- // @ts-ignore
324
- if (err.message === TaskTimoutFlag) {
325
- // @ts-ignore
326
- this.emit(Event.TaskTimeout, this._currentRetries, latency, this._taskTimeout)
327
- }
328
- throw err
329
- }
330
- // @ts-ignore
331
- if (task.failed) {
332
- lastError = task.error
333
- throw task.error
334
- }
335
- const rtnVal = task.result
336
- // @ts-ignore
337
- this.emit(Event.Success, rtnVal, this._currentRetries, latency)
338
-
339
- // Not AwaysTask, we can finish all the retries with success
340
- if (!AlwaysTask.isAlwaysTask(task)) {
341
- this._taskingFlag.resolve(rtnVal)
342
- break
343
- }
344
- // AwaysTask, continue to run the task
345
- } catch (e) {
346
- // @ts-ignore
347
- this.emit(Event.Failure, e, this._currentRetries, latency)
348
- }
349
- const nextRetries = ++this._currentRetries
350
- // next retry, max retries reached?
351
- if (this._currentRetries > this._maxRetries) {
352
- // @ts-ignore
353
- this.emit(Event.MaxRetries, nextRetries, this._maxRetries)
354
- // always task, treat as success, resolve the whole promise with <void>
355
- if (AlwaysTask.isAlwaysTask(task)) {
356
- this._taskingFlag.resolve()
357
- break
358
- }
359
- this._taskingFlag.reject(lastError ?? new Error(`Max Retries Exceeded, Retring ${this._currentRetries} times > max ${this._maxRetries}`))
360
- break
361
- }
362
- await PromiseUtils.delay(nextDelay)
363
- }
364
- this._taskingFlag.promise.finally(() => {
365
- this.resetRetryPolicy()
366
- this._taskingFlag = undefined
367
- const spent = Date.now() - startAt
368
- // @ts-ignore
369
- this.emit(Event.Completed, this._currentRetries, spent)
370
- })
371
- return this._taskingFlag.promise
372
- }
373
-
374
- /**
375
- * Stops the retrier with an optional reason. If already stopping, returns the existing break promise.
376
- * @param {Error} [reason] - Optional reason for stopping (defaults to 'Manually Stop' error).
377
- * @returns {Promise<void>} A promise that resolves when the retrier has fully stopped.
378
- */
379
- async stop (reason) {
380
- // @ts-ignore
381
- this.emit(Event.Stop, reason)
382
- if (this._taskingFlag == null) {
383
- return // no task running
384
- }
385
- if (this._breakFlag != null) {
386
- // @ts-ignore
387
- return this._breakFlag.promise
388
- }
389
- this._breakFlag = PromiseUtils.defer()
390
- this._breakReason = reason ?? new Error('Manually Stop')
391
- // @ts-ignore
392
- this.once(Event.Completed, () => {
393
- // @ts-ignore
394
- this._breakFlag.resolve()
395
- })
396
-
397
- // @ts-ignore
398
- this._breakFlag.promise.finally(() => {
399
- this._breakFlag = undefined
400
- this._breakReason = undefined
401
- })
402
- return this._breakFlag.promise
403
- }
404
-
405
- /**
406
- * Resets the retry policy to its initial state.
407
- */
408
- resetRetryPolicy () {
409
- this._policy.reset()
410
- }
411
-
412
- /**
413
- * Registers a listener function to be called on "retry" events.
414
- * @param {Function} listener - The callback function
415
- * @returns {this}
416
- */
417
- onRetry (listener) {
418
- // @ts-ignore
419
- this.on(Event.Retry, listener)
420
- return this
421
- }
422
-
423
- /**
424
- * Registers a listener for "error" events.
425
- * @param {Function} listener - The callback function
426
- * @returns {this}
427
- */
428
- onError (listener) {
429
- // @ts-ignore
430
- this.on(Event.Error, listener)
431
- return this
432
- }
433
-
434
- /**
435
- * Registers a listener for "failure" events.
436
- * @param {Function} listener - The callback function
437
- * @returns {this}
438
- */
439
- onFailure (listener) {
440
- // @ts-ignore
441
- this.on(Event.Failure, listener)
442
- return this
443
- }
444
-
445
- /**
446
- * Registers a listener for "success" events.
447
- * @param {Function} listener - The callback function
448
- * @returns {this}
449
- */
450
- onSuccess (listener) {
451
- // @ts-ignore
452
- this.on(Event.Success, listener)
453
- return this
454
- }
455
-
456
- /**
457
- * Registers a listener for "start" events.
458
- * @param {Function} listener - The callback function
459
- * @returns {this}
460
- */
461
- onStart (listener) {
462
- // @ts-ignore
463
- this.on(Event.Start, listener)
464
- return this
465
- }
466
-
467
- /**
468
- * Registers a listener for "stop" events.
469
- * @param {Function} listener - The callback function
470
- * @returns {this}
471
- */
472
- onStop (listener) {
473
- // @ts-ignore
474
- this.on(Event.Stop, listener)
475
- return this
476
- }
477
-
478
- /**
479
- * Registers a listener for "timeout" events.
480
- * @param {Function} listener - The callback function
481
- */
482
- onTimeout (listener) {
483
- // @ts-ignore
484
- this.on(Event.Timeout, listener)
485
- return this
486
- }
487
-
488
- /**
489
- * Registers a listener for "task-timeout" events.
490
- * @param {Function} listener - The callback function
491
- * @returns {this}
492
- */
493
- onTaskTimeout (listener) {
494
- // @ts-ignore
495
- this.on(Event.TaskTimeout, listener)
496
- return this
497
- }
498
-
499
- /**
500
- * Registers a listener for "completed" events.
501
- * @param {Function} listener - The callback function
502
- */
503
- onCompleted (listener) {
504
- // @ts-ignore
505
- this.on(Event.Completed, listener)
506
- return this
507
- }
508
-
509
- /**
510
- * Registers a listener for the 'MaxRetries' event.
511
- * @param {Function} listener - The callback function to be executed when max retries are reached.
512
- * @returns {this}
513
- */
514
- onMaxRetries (listener) {
515
- // @ts-ignore
516
- this.on(Event.MaxRetries, listener)
517
- return this
518
- }
519
- }
520
-
521
- /**
522
- * Checks if a value represents infinity or a non-positive number.
523
- * @param {number} value - The value to check
524
- * @returns {boolean} True if the value is <= 0 or Infinity, false otherwise
525
- */
526
- function isInfinite (value) {
527
- return value <= 0 || value === Infinity
528
- }
529
-
530
- export { Retrier as RetrierType }
package/lib/task.js DELETED
@@ -1,58 +0,0 @@
1
- import { TypeAssert } from '@creejs/commons-lang'
2
- // owned
3
-
4
- /**
5
- * @typedef {import('./retrier.js').default} Retrier
6
- */
7
-
8
- // module vars
9
- const { assertNotNil, assertFunction } = TypeAssert
10
- export default class Task {
11
- /**
12
- * Creates a new Task instance.
13
- * @param {Retrier} retrier - The retrier instance.
14
- * @param {Function} task - The function to be executed as the task.
15
- */
16
- constructor (retrier, task) {
17
- assertNotNil(retrier, 'retrier')
18
- assertFunction(task, 'task')
19
- this.retrier = retrier
20
- this.task = task
21
- this.result = undefined
22
- this.error = undefined
23
- }
24
-
25
- get failed () {
26
- return this.error != null
27
- }
28
-
29
- get succeeded () {
30
- return this.error == null
31
- }
32
-
33
- /**
34
- * Executes the task with the given retry parameters.
35
- * 1. if execution throw error, keep error in this.error
36
- * 2. if execution return value, keep it in this.result
37
- * 3. always return Promise<void>
38
- * @param {number} retries - The number of retries attempted so far.
39
- * @param {number} latence - The current latency ms.
40
- * @param {number} nextInterval - The next interval ms.
41
- * @returns {Promise<void>} The result of the task execution.
42
- */
43
- async execute (retries, latence, nextInterval) {
44
- try {
45
- this.result = await this.task(retries, latence, nextInterval)
46
- this.error = undefined
47
- } catch (e) {
48
- this.error = e
49
- }
50
- }
51
-
52
- dispose () {
53
- // @ts-ignore
54
- this.retrier = undefined
55
- }
56
- }
57
-
58
- export { Task as TaskType }