charles
2024-04-29 c7f3fd5215399b37d0511b3bd555150ff1b13507
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
type ResolveFn<T = string> = (value: T | PromiseLike<T>) => void
 
type RejectFn = (reason?: any) => void
 
type PerStepFn = (r: number) => void
/**
 * 步进计时器
 *
 * @example
 * const timer = new Timer(10_000, 1_000)
 * timer
 *   .start((round: number) => {
 *     console.log(round)
 *   })
 *   .then(
 *     () => {
 *       console.log('over')
 *     },
 *     (err) => {
 *       console.log(err)
 *     }
 *   )
 */
class StepTimer {
  private timer?: number
  /** 计时器总时长 单位毫秒 */
  private readonly duration: number = 0
  /** 计时步长 单位毫秒 */
  private readonly step: number = 1000
  /** 计时轮数 */
  private round: number = 0
  private blockFlag: boolean = false
  private reject?: RejectFn
  private perStep?: PerStepFn
  private resolve?: ResolveFn
 
  /**
   * 创建计时器
   * @param duration 计时器总持续时间
   * @param step 计时间隔
   */
  constructor(duration: number, step: number) {
    this.duration = duration
    this.step = step
    this.round = 0
  }
 
  /**
   * 启动计时器
   * 接受一个函数, 每轮触发一次
   * 返回 Promise, 在计时结束后 resolve
   * 重复调用会清除之前的计时器重新计时
   * @param perStep
   */
  start(perStep: PerStepFn): Promise<string> {
    if (this.timer) {
      this.reset()
    }
    this.perStep = perStep
    return new Promise((resolve, reject) => {
      this.resolve = resolve
      this.reject = reject
      this.createIntervalTimer(this.duration)
    })
  }
 
  private createIntervalTimer(duration: number) {
    this.timer = setInterval(() => {
      this.round++
      if (this.step * this.round < duration) {
        if (this.blockFlag) {
          this.round--
          clearInterval(this.timer)
          return
        }
 
        try {
          this.perStep!(this.round)
        } catch (e) {
          this.reject!(e)
        }
      } else if (this.step * this.round === duration) {
        if (this.blockFlag) {
          this.round--
          clearInterval(this.timer)
          return
        }
 
        try {
          this.perStep!(this.round)
        } catch (e) {
          this.reject!(e)
        }
        clearInterval(this.timer)
        this.resolve!('time over')
      } else {
        clearInterval(this.timer)
        this.resolve!('time over')
      }
    }, this.step)
  }
 
  pause() {
    if (!this.blockFlag) {
      this.blockFlag = true
    }
  }
 
  continue() {
    if (this.blockFlag) {
      this.blockFlag = false
      clearInterval(this.timer)
      const newDuration = this.duration - this.round * this.step
      this.createIntervalTimer(newDuration)
    }
  }
 
  /**
   * 提前终止计时器
   */
  abort() {
    this.reject?.('abort')
    this.reset()
  }
 
  /**
   * 销毁定时器
   */
  destroy() {
    this.reset()
  }
 
  private reset() {
    this.blockFlag = false
    this.round = 0
    clearInterval(this.timer)
    this.timer = undefined
    this.reject?.('restart')
  }
}
 
export { StepTimer }