@leafer/task 1.0.0-beta.15 → 1.0.0-beta.17

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/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@leafer/task",
3
- "version": "1.0.0-beta.15",
3
+ "version": "1.0.0-beta.17",
4
4
  "description": "@leafer/task",
5
5
  "author": "Chao (Leafer) Wan",
6
6
  "license": "MIT",
7
7
  "main": "src/index.ts",
8
8
  "types": "types/index.d.ts",
9
9
  "files": [
10
+ "src",
10
11
  "types",
11
12
  "dist"
12
13
  ],
@@ -21,10 +22,10 @@
21
22
  "leaferjs"
22
23
  ],
23
24
  "dependencies": {
24
- "@leafer/math": "1.0.0-beta.15",
25
- "@leafer/debug": "1.0.0-beta.15"
25
+ "@leafer/math": "1.0.0-beta.17",
26
+ "@leafer/debug": "1.0.0-beta.17"
26
27
  },
27
28
  "devDependencies": {
28
- "@leafer/interface": "1.0.0-beta.15"
29
+ "@leafer/interface": "1.0.0-beta.17"
29
30
  }
30
31
  }
@@ -0,0 +1,47 @@
1
+ import { IFunction, ITaskItem } from '@leafer/interface'
2
+ import { IncrementId } from '@leafer/math'
3
+ import { Debug } from '@leafer/debug'
4
+
5
+ import { TaskProcessor } from './TaskProcessor'
6
+
7
+
8
+ const debug = Debug.get('TaskProcessor')
9
+ export class TaskItem implements ITaskItem {
10
+
11
+ readonly id: number
12
+
13
+ public parent: TaskProcessor
14
+
15
+ public parallel = true
16
+ public time = 1 // 预估任务需要运行的时间, 毫秒为单位
17
+
18
+ public isComplete: boolean
19
+ public isCancel: boolean
20
+
21
+ private task: IFunction
22
+
23
+ constructor(task?: IFunction) {
24
+ this.id = IncrementId.create(IncrementId.TASK)
25
+ this.task = task
26
+ }
27
+
28
+ async run(): Promise<void> {
29
+ try {
30
+ if (this.task && !this.isComplete && this.parent.running) await this.task()
31
+ } catch (error) {
32
+ debug.error(error)
33
+ }
34
+ }
35
+
36
+ public complete(): void {
37
+ this.isComplete = true
38
+ this.parent = null
39
+ this.task = null
40
+ }
41
+
42
+ public cancel(): void {
43
+ this.isCancel = true
44
+ this.complete()
45
+ }
46
+
47
+ }
@@ -0,0 +1,286 @@
1
+ import { IFunction, ITaskProcessor, ITaskProcessorConfig, ITaskOptions, ITaskItem } from '@leafer/interface'
2
+ import { DataHelper } from '@leafer/data'
3
+
4
+ import { TaskItem } from './TaskItem'
5
+
6
+
7
+ export class TaskProcessor implements ITaskProcessor {
8
+
9
+ public config: ITaskProcessorConfig = { parallel: 6 }
10
+
11
+ protected list: ITaskItem[] = []
12
+
13
+ protected parallelList: ITaskItem[]
14
+ protected parallelSuccessNumber: number
15
+
16
+ public running = false
17
+ public isComplete = true
18
+
19
+ protected timer: any
20
+
21
+ public get total(): number {
22
+ return this.list.length + this.delayNumber
23
+ }
24
+
25
+ public index = 0
26
+
27
+ public delayNumber = 0 // 延迟执行任务
28
+
29
+ public get finishedIndex(): number {
30
+ return this.isComplete ? 0 : this.index + this.parallelSuccessNumber
31
+ }
32
+
33
+ public get remain(): number {
34
+ return this.isComplete ? this.total : this.total - this.finishedIndex
35
+ }
36
+
37
+ public get percent(): number {
38
+ const { total } = this
39
+ let totalTime = 0, runTime = 0
40
+
41
+ for (let i = 0; i < total; i++) {
42
+ if (i <= this.finishedIndex) {
43
+ runTime += this.list[i].time
44
+ if (i === this.finishedIndex) totalTime = runTime
45
+ } else {
46
+ totalTime += this.list[i].time
47
+ }
48
+ }
49
+
50
+ return this.isComplete ? 1 : (runTime / totalTime)
51
+ }
52
+
53
+
54
+ constructor(config?: ITaskProcessorConfig) {
55
+ if (config) DataHelper.assign(this.config, config)
56
+ this.empty()
57
+ }
58
+
59
+ // list
60
+
61
+ public add(taskCallback: IFunction, options?: ITaskOptions | number): ITaskItem {
62
+ let start: boolean, parallel: boolean, time: number, delay: number
63
+
64
+ const task = new TaskItem(taskCallback)
65
+ task.parent = this
66
+
67
+ if (typeof options === 'number') {
68
+ delay = options
69
+ } else if (options) {
70
+ parallel = options.parallel
71
+ start = options.start
72
+ time = options.time
73
+ delay = options.delay
74
+ }
75
+
76
+ if (time) task.time = time
77
+ if (parallel === false) task.parallel = false
78
+
79
+ if (delay === undefined) {
80
+ this.push(task, start)
81
+ } else {
82
+ this.delayNumber++
83
+ setTimeout(() => {
84
+ this.delayNumber--
85
+ this.push(task, start)
86
+ }, delay)
87
+ }
88
+
89
+ this.isComplete = false
90
+
91
+ return task
92
+ }
93
+
94
+ protected push(task: ITaskItem, start?: boolean): void {
95
+ this.list.push(task)
96
+ if (start !== false && !this.timer) {
97
+ this.timer = setTimeout(() => this.start())
98
+ }
99
+ }
100
+
101
+ protected empty(): void {
102
+ this.index = 0
103
+ this.parallelSuccessNumber = 0
104
+ this.list = []
105
+ this.parallelList = []
106
+ }
107
+
108
+ // control
109
+
110
+ public start(): void {
111
+ if (!this.running) {
112
+ this.running = true
113
+ this.isComplete = false
114
+ this.run()
115
+ }
116
+ }
117
+
118
+ public pause(): void {
119
+ clearTimeout(this.timer)
120
+ this.timer = null
121
+ this.running = false
122
+ }
123
+
124
+ public resume(): void {
125
+ this.start()
126
+ }
127
+
128
+ public skip(): void {
129
+ this.index++
130
+ this.resume()
131
+ }
132
+
133
+ public stop(): void {
134
+ this.isComplete = true
135
+ this.list.forEach(task => { if (!task.isComplete) task.cancel() })
136
+ this.pause()
137
+ this.empty()
138
+ }
139
+
140
+ // run
141
+
142
+ protected run(): void {
143
+ if (!this.running) return
144
+
145
+ this.setParallelList()
146
+
147
+ if (this.parallelList.length > 1) {
148
+
149
+ this.runParallelTasks()
150
+
151
+ } else {
152
+
153
+ this.remain ? this.runTask() : this.onComplete()
154
+
155
+ }
156
+ }
157
+
158
+ protected runTask(): void {
159
+ const task = this.list[this.index]
160
+ if (!task) {
161
+ this.nextTask() // 存在延时任务
162
+ return
163
+ }
164
+ task.run().then(() => {
165
+
166
+ this.onTask(task)
167
+
168
+ this.index++
169
+ this.nextTask()
170
+
171
+ }).catch(error => {
172
+ this.onError(error)
173
+ })
174
+ }
175
+
176
+ protected runParallelTasks(): void {
177
+ this.parallelList.forEach(task => this.runParallelTask(task))
178
+ }
179
+
180
+ protected runParallelTask(task: ITaskItem): void {
181
+ task.run().then(() => {
182
+
183
+ this.onTask(task)
184
+ this.fillParallelTask()
185
+
186
+ }).catch(error => {
187
+ this.onParallelError(error)
188
+ })
189
+ }
190
+
191
+ private nextTask(): void {
192
+ if (this.total === this.finishedIndex) {
193
+ this.onComplete()
194
+ } else {
195
+ this.timer = setTimeout(() => this.run())
196
+ }
197
+ }
198
+
199
+ protected setParallelList(): void {
200
+ let task: ITaskItem
201
+
202
+ this.parallelList = []
203
+ this.parallelSuccessNumber = 0
204
+ let end = this.index + this.config.parallel
205
+
206
+ if (end > this.list.length) end = this.list.length
207
+
208
+ for (let i = this.index; i < end; i++) {
209
+ task = this.list[i]
210
+ if (task.parallel) {
211
+ this.parallelList.push(task)
212
+ } else {
213
+ break
214
+ }
215
+ }
216
+ }
217
+
218
+
219
+ protected fillParallelTask(): void {
220
+ let task: ITaskItem
221
+ const parallelList = this.parallelList
222
+
223
+ // 完成一个任务
224
+ this.parallelSuccessNumber++
225
+ parallelList.pop()
226
+
227
+ // 找到下一个可以并行的任务
228
+ const parallelWaitNumber = parallelList.length
229
+ const nextIndex = this.finishedIndex + parallelWaitNumber
230
+
231
+ if (parallelList.length) {
232
+
233
+ if (!this.running) return
234
+
235
+ if (nextIndex < this.total) {
236
+
237
+ task = this.list[nextIndex]
238
+
239
+ if (task && task.parallel) {
240
+ parallelList.push(task)
241
+ this.runParallelTask(task)
242
+ }
243
+
244
+ }
245
+
246
+ } else {
247
+
248
+ this.index += this.parallelSuccessNumber
249
+ this.parallelSuccessNumber = 0
250
+ this.nextTask()
251
+
252
+ }
253
+ }
254
+
255
+ // event
256
+
257
+ protected onComplete(): void {
258
+ this.stop()
259
+ if (this.config.onComplete) this.config.onComplete()
260
+ }
261
+
262
+ protected onTask(task: ITaskItem): void {
263
+ task.complete()
264
+ if (this.config.onTask) this.config.onTask()
265
+ }
266
+
267
+ protected onParallelError(error: unknown): void {
268
+ // 并行变串行, 以便下次重试
269
+ this.parallelList.forEach(task => {
270
+ task.parallel = false
271
+ })
272
+ this.parallelList.length = 0
273
+ this.parallelSuccessNumber = 0
274
+
275
+ this.onError(error)
276
+ }
277
+
278
+ protected onError(error: unknown): void {
279
+ this.pause()
280
+ if (this.config.onError) this.config.onError(error)
281
+ }
282
+
283
+ public destroy(): void {
284
+ this.stop()
285
+ }
286
+ }