-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathtimer.ts
137 lines (122 loc) · 3.27 KB
/
timer.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
import { AnyFunction } from './function'
/**
* Sleep millseconds
*
* @param timeout - sleep millseconds
* @example ```ts
* await sleep(100)
* ```
*/
export function sleep(timeout: number): Promise<void> {
if(timeout < 0) throw new Error(`timeout should greate then 0, got ${timeout}`)
return new Promise(res => setTimeout(res, timeout))
}
/**
* Function wrapper, call delay millseconds
*
* @param fn - wrapp function
* @param timeout - millseconds
* @returns function
* @example ```ts
* const fn = () => 42
* const delay_fn = delay(fn, 1000)
* await delay_fn() // 42, will exec after one second
* ```
*/
export function delay<F extends AnyFunction>(fn: F, timeout: number) {
if(timeout < 0) throw new Error(`timeout should greate then 0, got ${timeout}`)
return (...args: Parameters<F>): Promise<ReturnType<F>> => {
return new Promise(res => setTimeout(() => {
res(fn(args))
}, timeout))
}
}
/**
* Function wrapper, throw when overtime
*
* @param fn - wrap function
* @param timeout - millseconds
* @throws timeout
* @returns function
* @example ```ts
* const fn = () => 42
* const timeout_fn = timeout(fn, 1000)
* await timeout_fn() // 42
*
* const fn = () => sleep(2000)
* const timeout_fn = timeout(fn, 1000)
* await timeout_fn() // throw Timeout
* ```
*/
export function timeout<F extends AnyFunction>(fn: F, timeout: number) {
return (...args: Parameters<F>): Promise<ReturnType<F>> => {
return new Promise((res, rej) => {
const timer = setTimeout(cleanup, timeout)
res(fn(...args))
function cleanup() {
clearTimeout(timer)
rej(new Error('Timeout'))
}
})
}
}
/** build-in timing function */
export const enum TimingFunction {
/* change by steps */
Step,
/* linear, interval no changed */
Linear,
/* curve, fast forward then slow out */
Curve
}
const TIMING_FUNCTION: { [K in TimingFunction]: ITimingFunction } = {
[TimingFunction.Step]: a => {
if(a <= 3) return 0
else if(a <= 6) return 1
else return 2
},
[TimingFunction.Linear]: a => a,
[TimingFunction.Curve]: a => Math.log1p(a)
}
/**
* timing function type
*
* @param times current run times
* @param options retry options
*/
export interface ITimingFunction {
(times: number, options: Options): number
}
/** retry options type */
export interface Options {
/** base interval, default to 1s */
base?: number,
/** timing function, use build-in or custom equation, default to `TimingFunction.Linear` */
timing?: TimingFunction | ITimingFunction
}
/**
* retry function that not throw
*
* @param fn caller
* @param options retry options
*/
export function retry_by_timer<F extends AnyFunction<R>, R>(fn: F, max: number = 10, options: Options = {}) {
return async (...args: Parameters<F>): Promise<ReturnType<F>> => {
const {
base = 1000,
timing = TimingFunction.Linear
} = options
let times: number = 0
const func: ITimingFunction = 'function' === typeof timing ? timing : TIMING_FUNCTION[timing]
while(times < max) {
try {
const dt = func(times, options)
const ms: number = (dt + 1) * base
return await delay(fn, ms)(...args)
} catch(e) {
times++
}
}
throw new Error('Maximum retry times')
}
}