@guanghechen/task 1.0.0-alpha.9 → 1.0.0-beta.1

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/lib/cjs/index.cjs CHANGED
@@ -1,139 +1,127 @@
1
1
  'use strict';
2
2
 
3
- var constant = require('@guanghechen/constant');
4
- var error = require('@guanghechen/error');
5
- var monitor = require('@guanghechen/monitor');
3
+ var observable = require('@guanghechen/observable');
4
+ var error_types = require('@guanghechen/error.types');
6
5
 
7
- function noop(..._args) { }
6
+ exports.TaskStrategyEnum = void 0;
7
+ (function (TaskStrategyEnum) {
8
+ TaskStrategyEnum[TaskStrategyEnum["ABORT_ON_ERROR"] = 1] = "ABORT_ON_ERROR";
9
+ TaskStrategyEnum[TaskStrategyEnum["CONTINUE_ON_ERROR"] = 2] = "CONTINUE_ON_ERROR";
10
+ })(exports.TaskStrategyEnum || (exports.TaskStrategyEnum = {}));
11
+ exports.TaskStatusEnum = void 0;
12
+ (function (TaskStatusEnum) {
13
+ TaskStatusEnum[TaskStatusEnum["PENDING"] = 1] = "PENDING";
14
+ TaskStatusEnum[TaskStatusEnum["RUNNING"] = 2] = "RUNNING";
15
+ TaskStatusEnum[TaskStatusEnum["SUSPENDED"] = 4] = "SUSPENDED";
16
+ TaskStatusEnum[TaskStatusEnum["CANCELLED"] = 8] = "CANCELLED";
17
+ TaskStatusEnum[TaskStatusEnum["FAILED"] = 16] = "FAILED";
18
+ TaskStatusEnum[TaskStatusEnum["COMPLETED"] = 32] = "COMPLETED";
19
+ TaskStatusEnum[TaskStatusEnum["ATTEMPT_SUSPENDING"] = 64] = "ATTEMPT_SUSPENDING";
20
+ TaskStatusEnum[TaskStatusEnum["ATTEMPT_RESUMING"] = 128] = "ATTEMPT_RESUMING";
21
+ TaskStatusEnum[TaskStatusEnum["ATTEMPT_CANCELING"] = 256] = "ATTEMPT_CANCELING";
22
+ TaskStatusEnum[TaskStatusEnum["ATTEMPT_COMPLETING"] = 512] = "ATTEMPT_COMPLETING";
23
+ })(exports.TaskStatusEnum || (exports.TaskStatusEnum = {}));
8
24
 
9
- const active = constant.TaskStatusEnum.RUNNING |
10
- constant.TaskStatusEnum.ATTEMPT_SUSPENDING |
11
- constant.TaskStatusEnum.ATTEMPT_RESUMING;
12
- const alive = constant.TaskStatusEnum.PENDING |
13
- constant.TaskStatusEnum.RUNNING |
14
- constant.TaskStatusEnum.SUSPENDED |
15
- constant.TaskStatusEnum.ATTEMPT_SUSPENDING |
16
- constant.TaskStatusEnum.ATTEMPT_RESUMING;
17
- const terminated = constant.TaskStatusEnum.CANCELLED |
18
- constant.TaskStatusEnum.FAILED |
19
- constant.TaskStatusEnum.FINISHED;
20
- class TaskState {
21
- name;
22
- _errorCollector;
23
- _monitorAddError;
24
- _monitorStatusChange;
25
- _status;
26
- constructor(name) {
27
- this.name = name;
28
- this._errorCollector = new error.SoraErrorCollector(name);
29
- this._monitorAddError = new monitor.Monitor('onAddError');
30
- this._monitorStatusChange = new monitor.Monitor('onStatusChange');
31
- this._status = constant.TaskStatusEnum.PENDING;
32
- }
33
- get status() {
34
- return this._status;
35
- }
36
- get active() {
37
- return (this._status & active) > 0;
25
+ const _terminated = exports.TaskStatusEnum.CANCELLED | exports.TaskStatusEnum.FAILED | exports.TaskStatusEnum.COMPLETED;
26
+ const _transitionMap = {
27
+ [exports.TaskStatusEnum.PENDING]: exports.TaskStatusEnum.PENDING |
28
+ exports.TaskStatusEnum.RUNNING |
29
+ exports.TaskStatusEnum.CANCELLED |
30
+ exports.TaskStatusEnum.ATTEMPT_CANCELING,
31
+ [exports.TaskStatusEnum.RUNNING]: exports.TaskStatusEnum.RUNNING |
32
+ exports.TaskStatusEnum.SUSPENDED |
33
+ _terminated |
34
+ exports.TaskStatusEnum.ATTEMPT_SUSPENDING |
35
+ exports.TaskStatusEnum.ATTEMPT_CANCELING |
36
+ exports.TaskStatusEnum.ATTEMPT_COMPLETING,
37
+ [exports.TaskStatusEnum.SUSPENDED]: exports.TaskStatusEnum.RUNNING |
38
+ exports.TaskStatusEnum.SUSPENDED |
39
+ _terminated |
40
+ exports.TaskStatusEnum.ATTEMPT_RESUMING |
41
+ exports.TaskStatusEnum.ATTEMPT_CANCELING |
42
+ exports.TaskStatusEnum.ATTEMPT_COMPLETING,
43
+ [exports.TaskStatusEnum.CANCELLED]: exports.TaskStatusEnum.CANCELLED,
44
+ [exports.TaskStatusEnum.FAILED]: exports.TaskStatusEnum.FAILED,
45
+ [exports.TaskStatusEnum.COMPLETED]: exports.TaskStatusEnum.COMPLETED,
46
+ [exports.TaskStatusEnum.ATTEMPT_SUSPENDING]: exports.TaskStatusEnum.SUSPENDED |
47
+ _terminated |
48
+ exports.TaskStatusEnum.ATTEMPT_SUSPENDING |
49
+ exports.TaskStatusEnum.ATTEMPT_CANCELING |
50
+ exports.TaskStatusEnum.ATTEMPT_COMPLETING,
51
+ [exports.TaskStatusEnum.ATTEMPT_RESUMING]: exports.TaskStatusEnum.RUNNING |
52
+ _terminated |
53
+ exports.TaskStatusEnum.ATTEMPT_RESUMING |
54
+ exports.TaskStatusEnum.ATTEMPT_CANCELING |
55
+ exports.TaskStatusEnum.ATTEMPT_COMPLETING,
56
+ [exports.TaskStatusEnum.ATTEMPT_CANCELING]: _terminated | exports.TaskStatusEnum.ATTEMPT_CANCELING,
57
+ [exports.TaskStatusEnum.ATTEMPT_COMPLETING]: _terminated | exports.TaskStatusEnum.ATTEMPT_COMPLETING,
58
+ };
59
+ class TaskStatus extends observable.Observable {
60
+ constructor() {
61
+ super(exports.TaskStatusEnum.PENDING);
38
62
  }
39
63
  get alive() {
40
- return (this._status & alive) > 0;
64
+ const value = this.getSnapshot();
65
+ return (value & _terminated) === 0;
41
66
  }
42
67
  get terminated() {
43
- return (this._status & terminated) > 0;
68
+ const value = this.getSnapshot();
69
+ return (value & _terminated) > 0;
44
70
  }
45
- get hasError() {
46
- return this._errorCollector.size > 0;
47
- }
48
- get error() {
49
- if (this._errorCollector.size === 0)
50
- return undefined;
51
- return { from: this.name, details: this._errorCollector.errors };
52
- }
53
- set status(status) {
54
- const curStatus = this._status;
55
- if (status !== curStatus) {
56
- const accepted = this.check(status);
57
- if (accepted) {
58
- this._status = status;
59
- this._monitorStatusChange.notify(status, curStatus);
60
- }
61
- else {
62
- throw new TypeError(`[transit] unexpected status: task(${this.name}) cur(${curStatus}) next(${status})`);
63
- }
71
+ next(nextStatus, options) {
72
+ const curStatus = this.getSnapshot();
73
+ if (this._verifyTransition(curStatus, nextStatus)) {
74
+ super.next(nextStatus, options);
75
+ if ((nextStatus & _terminated) > 0)
76
+ this.dispose();
77
+ return;
64
78
  }
65
- }
66
- monitor(monitor) {
67
- if (this.terminated)
68
- return noop;
69
- const { onAddError, onStatusChange } = monitor;
70
- const unsubscribeOnAddError = this._monitorAddError.subscribe(onAddError);
71
- const unsubscribeOnStatusChange = this._monitorStatusChange.subscribe(onStatusChange);
72
- return () => {
73
- unsubscribeOnAddError();
74
- unsubscribeOnStatusChange();
75
- };
76
- }
77
- check(nextStatus) {
78
- const status = this._status;
79
- switch (nextStatus) {
80
- case constant.TaskStatusEnum.PENDING:
81
- return false;
82
- case constant.TaskStatusEnum.RUNNING:
83
- return status === constant.TaskStatusEnum.PENDING || status === constant.TaskStatusEnum.ATTEMPT_RESUMING;
84
- case constant.TaskStatusEnum.SUSPENDED:
85
- return status === constant.TaskStatusEnum.ATTEMPT_SUSPENDING;
86
- case constant.TaskStatusEnum.CANCELLED:
87
- return status === constant.TaskStatusEnum.ATTEMPT_CANCELING;
88
- case constant.TaskStatusEnum.FAILED:
89
- return (status !== constant.TaskStatusEnum.PENDING &&
90
- status !== constant.TaskStatusEnum.SUSPENDED &&
91
- (status & terminated) === 0);
92
- case constant.TaskStatusEnum.FINISHED:
93
- return (status !== constant.TaskStatusEnum.PENDING &&
94
- status !== constant.TaskStatusEnum.SUSPENDED &&
95
- (status & terminated) === 0);
96
- case constant.TaskStatusEnum.ATTEMPT_SUSPENDING:
97
- return status === constant.TaskStatusEnum.RUNNING;
98
- case constant.TaskStatusEnum.ATTEMPT_RESUMING:
99
- return status === constant.TaskStatusEnum.SUSPENDED;
100
- case constant.TaskStatusEnum.ATTEMPT_CANCELING:
101
- return (status & alive) > 0;
102
- case constant.TaskStatusEnum.ATTEMPT_FINISHING:
103
- return status !== constant.TaskStatusEnum.PENDING && (status & alive) > 0;
104
- default:
105
- return false;
79
+ const strict = options?.strict ?? true;
80
+ if (strict) {
81
+ const curStatusName = exports.TaskStatusEnum[curStatus];
82
+ const nextStatusName = exports.TaskStatusEnum[nextStatus];
83
+ throw new RangeError(`Invalid status transition: ${curStatusName} -> ${nextStatusName}.`);
106
84
  }
107
85
  }
108
- cleanup() {
109
- if (!this.terminated)
110
- throw new Error(`[cleanup] task(${this.name}) is not terminated`);
111
- this._errorCollector.cleanup();
112
- this._monitorStatusChange.destroy();
113
- this._monitorAddError.destroy();
114
- }
115
- _addError(type, error, level = constant.ErrorLevelEnum.ERROR) {
116
- this._errorCollector.add(type, error, level);
117
- this._monitorAddError.notify(type, error, level);
86
+ _verifyTransition(curStatus, nextStatus) {
87
+ const validTransitions = _transitionMap[curStatus];
88
+ if (validTransitions === undefined)
89
+ return false;
90
+ return (nextStatus & validTransitions) > 0;
118
91
  }
119
92
  }
120
93
 
121
- class AtomicTask extends TaskState {
94
+ class AtomicTask {
95
+ name;
96
+ status;
97
+ strategy;
98
+ _errors;
122
99
  _promise;
123
- constructor(name) {
124
- super(name);
100
+ constructor(name, strategy) {
101
+ this.name = name;
102
+ this.strategy = strategy;
103
+ this.status = new TaskStatus();
104
+ this._errors = [];
125
105
  this._promise = undefined;
126
106
  }
107
+ get errors() {
108
+ return this._errors;
109
+ }
127
110
  async start() {
128
- if (this.status === constant.TaskStatusEnum.PENDING) {
129
- this.status = constant.TaskStatusEnum.RUNNING;
111
+ if (this.status.getSnapshot() === exports.TaskStatusEnum.PENDING) {
112
+ this.status.next(exports.TaskStatusEnum.RUNNING);
130
113
  this._promise = this.run()
131
114
  .then(() => {
132
- this.status = constant.TaskStatusEnum.FINISHED;
115
+ this.status.next(exports.TaskStatusEnum.COMPLETED);
133
116
  })
134
117
  .catch(error => {
135
- this._addError('AtomicTaskError', error);
136
- this.status = constant.TaskStatusEnum.FAILED;
118
+ const soraError = {
119
+ from: this.name,
120
+ level: error_types.ErrorLevelEnum.ERROR,
121
+ details: error,
122
+ };
123
+ this._errors.push(soraError);
124
+ this.status.next(exports.TaskStatusEnum.FAILED);
137
125
  });
138
126
  }
139
127
  return this._promise;
@@ -145,141 +133,166 @@ class AtomicTask extends TaskState {
145
133
  await this._promise;
146
134
  }
147
135
  async cancel() {
148
- if (this.status === constant.TaskStatusEnum.PENDING) {
149
- this.status = constant.TaskStatusEnum.ATTEMPT_CANCELING;
150
- this.status = constant.TaskStatusEnum.CANCELLED;
136
+ if (this.status.terminated)
151
137
  return;
152
- }
153
- if (this.alive)
154
- this.status = constant.TaskStatusEnum.ATTEMPT_CANCELING;
138
+ this.status.next(exports.TaskStatusEnum.ATTEMPT_CANCELING);
155
139
  await this._promise;
140
+ this.status.next(exports.TaskStatusEnum.CANCELLED, { strict: false });
156
141
  }
157
- async finish() {
158
- if (this.status === constant.TaskStatusEnum.PENDING)
142
+ async complete() {
143
+ const status = this.status;
144
+ if (status.getSnapshot() === exports.TaskStatusEnum.PENDING)
159
145
  await this.start();
160
- if (this.alive)
161
- this.status = constant.TaskStatusEnum.ATTEMPT_FINISHING;
146
+ if (status.terminated)
147
+ return;
148
+ status.next(exports.TaskStatusEnum.ATTEMPT_COMPLETING);
162
149
  await this._promise;
150
+ status.next(exports.TaskStatusEnum.COMPLETED, { strict: false });
163
151
  }
164
152
  }
165
153
 
166
- class ResumableTask extends TaskState {
154
+ class ResumableTask {
155
+ name;
156
+ status;
167
157
  strategy;
158
+ _errors;
168
159
  _pollInterval;
169
160
  _execution;
170
161
  _step;
171
- constructor(props) {
172
- super(props.name);
173
- this.strategy = props.strategy;
174
- this._pollInterval = Math.max(0, props.pollInterval);
162
+ constructor(name, strategy, pollInterval) {
163
+ this.name = name;
164
+ this.strategy = strategy;
165
+ this.status = new TaskStatus();
166
+ this._errors = [];
167
+ this._pollInterval = Math.max(0, pollInterval);
175
168
  this._execution = undefined;
176
169
  this._step = undefined;
177
170
  }
171
+ get errors() {
172
+ return this._errors;
173
+ }
178
174
  async start() {
179
- if (this.status === constant.TaskStatusEnum.PENDING) {
180
- this.status = constant.TaskStatusEnum.RUNNING;
175
+ if (this.status.getSnapshot() === exports.TaskStatusEnum.PENDING) {
176
+ this.status.next(exports.TaskStatusEnum.RUNNING);
181
177
  this._execution = this.run();
182
- this.launchStep();
183
- await this._step;
178
+ void this._launchStep();
184
179
  }
185
180
  }
186
181
  async pause() {
187
- if (this.status === constant.TaskStatusEnum.RUNNING) {
188
- this.status = constant.TaskStatusEnum.ATTEMPT_SUSPENDING;
182
+ if (this.status.getSnapshot() === exports.TaskStatusEnum.RUNNING) {
183
+ this.status.next(exports.TaskStatusEnum.ATTEMPT_SUSPENDING);
189
184
  await this._step;
190
- if (this.status === constant.TaskStatusEnum.ATTEMPT_SUSPENDING) {
191
- this.status = constant.TaskStatusEnum.SUSPENDED;
192
- }
185
+ this.status.next(exports.TaskStatusEnum.SUSPENDED, { strict: false });
193
186
  }
194
187
  }
195
188
  async resume() {
196
- if (this.status === constant.TaskStatusEnum.SUSPENDED) {
197
- this.status = constant.TaskStatusEnum.ATTEMPT_RESUMING;
189
+ if (this.status.getSnapshot() === exports.TaskStatusEnum.SUSPENDED) {
190
+ this.status.next(exports.TaskStatusEnum.ATTEMPT_RESUMING);
198
191
  await this._step;
199
- if (this.status === constant.TaskStatusEnum.ATTEMPT_RESUMING) {
200
- this.status = constant.TaskStatusEnum.RUNNING;
201
- this.queueStep();
192
+ if (this.status.getSnapshot() === exports.TaskStatusEnum.ATTEMPT_RESUMING) {
193
+ this.status.next(exports.TaskStatusEnum.RUNNING);
194
+ void this._queueStep();
202
195
  }
203
196
  }
204
197
  }
205
198
  async cancel() {
206
- if (this.alive) {
207
- this.status = constant.TaskStatusEnum.ATTEMPT_CANCELING;
208
- await this._step;
209
- if (this.status === constant.TaskStatusEnum.ATTEMPT_CANCELING) {
210
- this.status = constant.TaskStatusEnum.CANCELLED;
211
- }
212
- }
199
+ if (this.status.terminated)
200
+ return;
201
+ this.status.next(exports.TaskStatusEnum.ATTEMPT_CANCELING);
202
+ await this._step;
203
+ this.status.next(exports.TaskStatusEnum.CANCELLED, { strict: false });
213
204
  }
214
- async finish() {
215
- if (this.status === constant.TaskStatusEnum.PENDING)
205
+ async complete() {
206
+ const status = this.status;
207
+ if (status.getSnapshot() === exports.TaskStatusEnum.PENDING)
216
208
  await this.start();
217
- if (this.alive) {
218
- this.status = constant.TaskStatusEnum.ATTEMPT_FINISHING;
219
- await this._step;
220
- const execution = this._execution;
221
- if (execution) {
222
- while (this.status === constant.TaskStatusEnum.ATTEMPT_FINISHING) {
223
- const step = execution.next();
224
- if (step.done) {
225
- this.status = this.hasError ? constant.TaskStatusEnum.FAILED : constant.TaskStatusEnum.FINISHED;
226
- break;
227
- }
228
- await step.value.catch(error => {
229
- this._addError('ResumableTaskError', error);
230
- switch (this.strategy) {
231
- case constant.TaskStrategyEnum.ABORT_ON_ERROR:
232
- if (!this.terminated)
233
- this.status = constant.TaskStatusEnum.FAILED;
234
- break;
235
- case constant.TaskStrategyEnum.CONTINUE_ON_ERROR:
236
- break;
237
- }
238
- });
239
- }
240
- }
241
- }
242
- }
243
- launchStep() {
244
- if (this.status === constant.TaskStatusEnum.RUNNING && this._step === undefined && this._execution) {
245
- const step = this._execution.next();
209
+ if (status.terminated)
210
+ return;
211
+ status.next(exports.TaskStatusEnum.ATTEMPT_COMPLETING);
212
+ await this._step;
213
+ const execution = this._execution;
214
+ for (let alive = true; alive;) {
215
+ const step = execution.next();
246
216
  if (step.done) {
247
- this.status = this.hasError ? constant.TaskStatusEnum.FAILED : constant.TaskStatusEnum.FINISHED;
248
- return;
217
+ const nextStatus = this._errors.length > 0 ? exports.TaskStatusEnum.FAILED : exports.TaskStatusEnum.COMPLETED;
218
+ status.next(nextStatus, { strict: false });
219
+ break;
249
220
  }
250
- this._step = step.value
251
- .then(() => {
252
- this._step = undefined;
253
- this.queueStep();
254
- })
255
- .catch(error => {
256
- this._step = undefined;
257
- this._addError('ResumableTaskError', error);
221
+ try {
222
+ await step.value;
223
+ }
224
+ catch (error) {
225
+ const soraError = {
226
+ from: this.name,
227
+ level: error_types.ErrorLevelEnum.ERROR,
228
+ details: error,
229
+ };
230
+ this._errors.push(soraError);
258
231
  switch (this.strategy) {
259
- case constant.TaskStrategyEnum.ABORT_ON_ERROR:
260
- if (!this.terminated)
261
- this.status = constant.TaskStatusEnum.FAILED;
232
+ case exports.TaskStrategyEnum.ABORT_ON_ERROR:
233
+ status.next(exports.TaskStatusEnum.FAILED, { strict: false });
234
+ alive = false;
262
235
  break;
263
- case constant.TaskStrategyEnum.CONTINUE_ON_ERROR:
264
- this.queueStep();
236
+ case exports.TaskStrategyEnum.CONTINUE_ON_ERROR:
237
+ await this._queueStep();
265
238
  break;
266
239
  }
240
+ }
241
+ }
242
+ if (status.alive) {
243
+ const soraError = {
244
+ from: this.name,
245
+ level: error_types.ErrorLevelEnum.ERROR,
246
+ details: new RangeError('The task is not terminated.'),
247
+ };
248
+ this._errors.push(soraError);
249
+ }
250
+ }
251
+ async _launchStep() {
252
+ if (this._execution === undefined)
253
+ return;
254
+ if (this._step !== undefined)
255
+ return;
256
+ if (this.status.getSnapshot() !== exports.TaskStatusEnum.RUNNING)
257
+ return;
258
+ const execution = this._execution;
259
+ const step = execution.next();
260
+ if (step.done) {
261
+ this.status.next(this._errors.length > 0 ? exports.TaskStatusEnum.FAILED : exports.TaskStatusEnum.COMPLETED, {
262
+ strict: false,
267
263
  });
264
+ return;
265
+ }
266
+ this._step = step.value;
267
+ try {
268
+ await step.value;
269
+ this._step = undefined;
270
+ void this._queueStep();
271
+ }
272
+ catch (error) {
273
+ this._step = undefined;
274
+ const soraError = {
275
+ from: this.name,
276
+ level: error_types.ErrorLevelEnum.ERROR,
277
+ details: error,
278
+ };
279
+ this._errors.push(soraError);
280
+ switch (this.strategy) {
281
+ case exports.TaskStrategyEnum.ABORT_ON_ERROR:
282
+ this.status.next(exports.TaskStatusEnum.FAILED, { strict: false });
283
+ break;
284
+ case exports.TaskStrategyEnum.CONTINUE_ON_ERROR:
285
+ void this._queueStep();
286
+ break;
287
+ }
268
288
  }
269
289
  }
270
- queueStep() {
271
- setTimeout(() => this.launchStep(), this._pollInterval);
290
+ async _queueStep() {
291
+ await new Promise(resolve => setTimeout(resolve, this._pollInterval));
292
+ await this._launchStep();
272
293
  }
273
294
  }
274
295
 
275
- Object.defineProperty(exports, 'TaskStatusEnum', {
276
- enumerable: true,
277
- get: function () { return constant.TaskStatusEnum; }
278
- });
279
- Object.defineProperty(exports, 'TaskStrategyEnum', {
280
- enumerable: true,
281
- get: function () { return constant.TaskStrategyEnum; }
282
- });
283
296
  exports.AtomicTask = AtomicTask;
284
297
  exports.ResumableTask = ResumableTask;
285
- exports.TaskState = TaskState;
298
+ exports.TaskStatus = TaskStatus;