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