-
Notifications
You must be signed in to change notification settings - Fork 89
/
01_retry.ts
143 lines (121 loc) · 4.02 KB
/
01_retry.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
139
140
141
142
143
/*
Abstraction for a mechanism to perform actions repetitively until successful.
This module is split in 3 parts:
- the model
- primitives
- combinators
*/
// -------------------------------------------------------------------------------------
// model
// -------------------------------------------------------------------------------------
export interface RetryStatus {
/** Iteration number, where `0` is the first try */
readonly iterNumber: number
/** Latest attempt's delay. Will always be `undefined` on first run. */
readonly previousDelay: number | undefined
}
export const startStatus: RetryStatus = {
iterNumber: 0,
previousDelay: undefined
}
/**
* A `RetryPolicy` is a function that takes an `RetryStatus` and
* possibly returns a delay in milliseconds. Iteration numbers start
* at zero and increase by one on each retry. A *undefined* return value from
* the function implies we have reached the retry limit.
*/
export interface RetryPolicy {
(status: RetryStatus): number | undefined
}
// -------------------------------------------------------------------------------------
// primitives
// -------------------------------------------------------------------------------------
/**
* Constant delay with unlimited retries.
*/
export const constantDelay = (delay: number): RetryPolicy => () => delay
/**
* Retry immediately, but only up to `i` times.
*/
export const limitRetries = (i: number): RetryPolicy => (status) =>
status.iterNumber >= i ? undefined : 0
/**
* Grow delay exponentially each iteration.
* Each delay will increase by a factor of two.
*/
export const exponentialBackoff = (delay: number): RetryPolicy => (status) =>
delay * Math.pow(2, status.iterNumber)
// -------------------------------------------------------------------------------------
// combinators
// -------------------------------------------------------------------------------------
/**
* Set a time-upperbound for any delays that may be directed by the
* given policy.
*/
export const capDelay = (maxDelay: number) => (
policy: RetryPolicy
): RetryPolicy => (status) => {
const delay = policy(status)
return delay === undefined ? undefined : Math.min(maxDelay, delay)
}
/**
* Merges two policies. **Quiz**: what does it mean to merge two policies?
*/
export const concat = (second: RetryPolicy) => (
first: RetryPolicy
): RetryPolicy => (status) => {
const delay1 = first(status)
const delay2 = second(status)
if (delay1 !== undefined && delay2 !== undefined) {
return Math.max(delay1, delay2)
}
return undefined
}
// -------------------------------------------------------------------------------------
// tests
// -------------------------------------------------------------------------------------
/**
* Apply policy on status to see what the decision would be.
*/
export const applyPolicy = (policy: RetryPolicy) => (
status: RetryStatus
): RetryStatus => ({
iterNumber: status.iterNumber + 1,
previousDelay: policy(status)
})
/**
* Apply a policy keeping all intermediate results.
*/
export const dryRun = (policy: RetryPolicy): ReadonlyArray<RetryStatus> => {
const apply = applyPolicy(policy)
let status: RetryStatus = apply(startStatus)
const out: Array<RetryStatus> = [status]
while (status.previousDelay !== undefined) {
out.push((status = apply(out[out.length - 1])))
}
return out
}
import { pipe } from 'fp-ts/function'
/*
constantDelay(300)
|> concat(exponentialBackoff(200))
|> concat(limitRetries(5))
|> capDelay(2000)
*/
const myPolicy = pipe(
constantDelay(300),
concat(exponentialBackoff(200)),
concat(limitRetries(5)),
capDelay(2000)
)
console.log(dryRun(myPolicy))
/*
[
{ iterNumber: 1, previousDelay: 300 }, <= constantDelay
{ iterNumber: 2, previousDelay: 400 }, <= exponentialBackoff
{ iterNumber: 3, previousDelay: 800 }, <= exponentialBackoff
{ iterNumber: 4, previousDelay: 1600 }, <= exponentialBackoff
{ iterNumber: 5, previousDelay: 2000 }, <= capDelay
{ iterNumber: 6, previousDelay: undefined } <= limitRetries
]
*/