|
| 1 | +export abstract class Backoff { |
| 2 | + abstract getDelay(): number |
| 3 | + |
| 4 | + async wait() { |
| 5 | + const n = this.getDelay() |
| 6 | + if (n <= 0) { |
| 7 | + return |
| 8 | + } |
| 9 | + await new Promise(resolve => setTimeout(resolve, n)) |
| 10 | + } |
| 11 | +} |
| 12 | + |
| 13 | +export class FixedBackoff extends Backoff { |
| 14 | + private delay: number |
| 15 | + constructor(delay: number) { |
| 16 | + super() |
| 17 | + this.delay = delay |
| 18 | + } |
| 19 | + |
| 20 | + getDelay(): number { |
| 21 | + return this.delay |
| 22 | + } |
| 23 | +} |
| 24 | + |
| 25 | +export class ExponentialBackoff extends Backoff { |
| 26 | + private base: number |
| 27 | + private factor: number |
| 28 | + private next: number |
| 29 | + |
| 30 | + constructor(base: number, factor = 2) { |
| 31 | + super() |
| 32 | + this.base = base |
| 33 | + this.factor = factor |
| 34 | + this.next = base |
| 35 | + } |
| 36 | + |
| 37 | + getDelay(): number { |
| 38 | + const delay = this.next |
| 39 | + this.next *= this.factor |
| 40 | + return delay |
| 41 | + } |
| 42 | +} |
| 43 | + |
| 44 | +const Second = 1000 |
| 45 | + |
| 46 | +// make the backoff delay duration plus the delta, |
| 47 | +// delta is belong to (-delta, delta), not inclusive |
| 48 | +export class RandomizedBackoff extends Backoff { |
| 49 | + private backoff: Backoff |
| 50 | + private delta: number // int, in milliseconds |
| 51 | + |
| 52 | + constructor(backoff: Backoff, delta = 2 * Second) { |
| 53 | + super() |
| 54 | + this.backoff = backoff |
| 55 | + this.delta = Math.floor(delta) |
| 56 | + } |
| 57 | + |
| 58 | + getDelay(): number { |
| 59 | + let diff = Math.floor(Math.random() * this.delta) |
| 60 | + diff = Math.floor(Math.random() * 2) ? diff : -diff |
| 61 | + const delay = this.backoff.getDelay() + diff |
| 62 | + return Math.max(0, delay) |
| 63 | + } |
| 64 | +} |
| 65 | + |
| 66 | +export class LimitedBackoff extends Backoff { |
| 67 | + private backoff: Backoff |
| 68 | + private min: number |
| 69 | + private max: number |
| 70 | + |
| 71 | + constructor(backoff: Backoff, max: number, min = 0) { |
| 72 | + super() |
| 73 | + if (min > max) { |
| 74 | + throw new Error('min should be less than or equal to max') |
| 75 | + } |
| 76 | + this.backoff = backoff |
| 77 | + this.max = max |
| 78 | + this.min = min |
| 79 | + } |
| 80 | + |
| 81 | + getDelay(): number { |
| 82 | + let delay = Math.min( |
| 83 | + this.max, |
| 84 | + this.backoff.getDelay() |
| 85 | + ) |
| 86 | + delay = Math.max( |
| 87 | + this.min, |
| 88 | + delay |
| 89 | + ) |
| 90 | + return delay |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +export function getDefaultBackoff(): Backoff { |
| 95 | + const exponential = new ExponentialBackoff(3 * Second) |
| 96 | + const randomized = new RandomizedBackoff(exponential, Second) |
| 97 | + return new LimitedBackoff(randomized, 30 * Second) |
| 98 | +} |
0 commit comments