@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 +5 -4
- package/src/TaskItem.ts +47 -0
- package/src/TaskProcessor.ts +286 -0
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leafer/task",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
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.
|
|
25
|
-
"@leafer/debug": "1.0.0-beta.
|
|
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.
|
|
29
|
+
"@leafer/interface": "1.0.0-beta.17"
|
|
29
30
|
}
|
|
30
31
|
}
|
package/src/TaskItem.ts
ADDED
|
@@ -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
|
+
}
|