• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

panates / power-tasks / 16645936884

31 Jul 2025 10:00AM UTC coverage: 90.345% (+4.1%) from 86.218%
16645936884

push

github

erayhanoglu
1.11.1

248 of 284 branches covered (87.32%)

Branch coverage included in aggregate %.

800 of 876 relevant lines covered (91.32%)

62.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.31
/src/task.ts
1
import * as os from "os";
1✔
2
import { AsyncEventEmitter } from "strict-typed-events";
1✔
3
import { plural } from "./utils.js";
1✔
4

1✔
5
export type TaskFunction<T = any> = (args: TaskFunctionArgs) => T | Promise<T>;
1✔
6
export type TaskLike<T = any> = Task<T> | TaskFunction;
1✔
7
export type TaskStatus =
1✔
8
  | "idle"
1✔
9
  | "waiting"
1✔
10
  | "running"
1✔
11
  | "fulfilled"
1✔
12
  | "failed"
1✔
13
  | "aborting"
1✔
14
  | "aborted";
1✔
15
const osCPUs = os.cpus().length;
1✔
16

1✔
17
export interface TaskFunctionArgs {
1✔
18
  task: Task;
1✔
19
  signal: AbortSignal;
1✔
20
}
1✔
21

1✔
22
export interface TaskOptions {
1✔
23
  /**
1✔
24
   * Id of the task.
1✔
25
   */
1✔
26
  id?: any;
1✔
27

1✔
28
  /**
1✔
29
   * Name of the task. This value is used to for dependency tree
1✔
30
   */
1✔
31
  name?: string;
1✔
32

1✔
33
  /**
1✔
34
   * Arguments to be passed to task function.
1✔
35
   */
1✔
36
  args?: any[];
1✔
37

1✔
38
  /**
1✔
39
   * The list of child tasks
1✔
40
   */
1✔
41
  children?: TaskLike[] | (() => TaskLike[] | Promise<TaskLike[]>);
1✔
42

1✔
43
  /**
1✔
44
   * The list of tasks to be waited before running this task.
1✔
45
   */
1✔
46
  dependencies?: (Task | string)[];
1✔
47

1✔
48
  /**
1✔
49
   * Number of concurrent tasks to run in parallel
1✔
50
   */
1✔
51
  concurrency?: number;
1✔
52

1✔
53
  /**
1✔
54
   * Abort after first task failure
1✔
55
   */
1✔
56
  bail?: boolean;
1✔
57

1✔
58
  /**
1✔
59
   * Run tasks one by one
1✔
60
   */
1✔
61
  serial?: boolean;
1✔
62

1✔
63
  /**
1✔
64
   * Run the task exclusively. If set true, the tasks in the queue waits for this task to complete
1✔
65
   * even concurrency is greater than 1
1✔
66
   */
1✔
67
  exclusive?: boolean;
1✔
68

1✔
69
  /**
1✔
70
   * Time in milliseconds to wait for aborting tasks
1✔
71
   */
1✔
72
  abortTimeout?: number;
1✔
73

1✔
74
  onStart?: (task: Task) => void;
1✔
75
  onFinish?: (task: Task) => void;
1✔
76
  onRun?: (task: Task) => void;
1✔
77
  onStatusChange?: (task: Task) => void;
1✔
78
  onUpdate?: (task: Task, properties: string[]) => void;
1✔
79
  onUpdateRecursive?: (task: Task, properties: string[]) => void;
1✔
80
}
1✔
81

1✔
82
export interface TaskUpdateValues {
1✔
83
  status?: TaskStatus;
1✔
84
  message?: string;
1✔
85
  error?: any;
1✔
86
  result?: any;
1✔
87
  waitingFor?: boolean;
1✔
88
}
1✔
89

1✔
90
class TaskContext {
1✔
91
  // allTasks = new Set<Task>();
1✔
92
  executingTasks = new Set<Task>();
1✔
93
  queue = new Set<Task>();
1✔
94
  concurrency!: number;
1✔
95
  triggerPulse!: () => void;
1✔
96
}
1✔
97

1✔
98
const noOp = () => undefined;
1✔
99
const taskContextKey = Symbol.for("power-tasks.Task.context");
1✔
100

1✔
101
let idGen = 0;
1✔
102

1✔
103
export class Task<T = any> extends AsyncEventEmitter {
1✔
104
  protected [taskContextKey]?: TaskContext;
1✔
105
  protected _id = "";
1✔
106
  protected _options: TaskOptions;
1✔
107
  protected _executeFn?: TaskFunction;
1✔
108
  protected _children?: Task[];
1✔
109
  protected _dependencies?: Task[];
1✔
110
  protected _status: TaskStatus = "idle";
1✔
111
  protected _message?: string;
1✔
112
  protected _executeDuration?: number;
1✔
113
  protected _error?: any;
1✔
114
  protected _result?: T;
1✔
115
  protected _isManaged?: boolean;
1✔
116
  protected _abortController = new AbortController();
1✔
117
  protected _abortTimer?: NodeJS.Timeout;
1✔
118
  protected _waitingFor?: Set<Task>;
1✔
119
  protected _failedChildren?: Task[];
1✔
120
  protected _abortedChildren?: Task[];
1✔
121
  protected _failedDependencies?: Task[];
1✔
122
  protected _childrenLeft?: Set<Task>;
1✔
123

1✔
124
  constructor(children: TaskLike[], options?: Omit<TaskOptions, "children">);
1✔
125
  constructor(execute: TaskFunction, options?: TaskOptions);
1✔
126
  constructor(arg0: any, options?: TaskOptions) {
1✔
127
    super();
100✔
128
    this.setMaxListeners(100);
100✔
129
    options = options || {};
100✔
130
    if (Array.isArray(arg0)) {
100✔
131
      options.children = arg0;
10✔
132
    } else this._executeFn = arg0;
100✔
133
    this._options = { ...options };
100✔
134
    this._id = this._options.id || "";
100✔
135
    if (this._options.bail == null) this._options.bail = true;
100✔
136
    if (options.onStart) this.on("start", options.onStart);
100!
137
    if (options.onFinish) this.on("finish", options.onFinish);
100!
138
    if (options.onRun) this.on("run", options.onRun);
100!
139
    if (options.onStatusChange)
100✔
140
      this.on("status-change", options.onStatusChange);
100!
141
    if (options.onUpdate) this.on("update", options.onUpdate);
100!
142
    if (options.onUpdateRecursive)
100✔
143
      this.on("update-recursive", options.onUpdateRecursive);
100✔
144
  }
100✔
145

1✔
146
  get id(): string {
1✔
147
    return this._id;
117✔
148
  }
117✔
149

1✔
150
  get name(): string | undefined {
1✔
151
    return this._options.name;
221✔
152
  }
221✔
153

1✔
154
  get children(): Task[] | undefined {
1✔
155
    return this._children;
39✔
156
  }
39✔
157

1✔
158
  get options(): TaskOptions {
1✔
159
    return this._options;
667✔
160
  }
667✔
161

1✔
162
  get message(): string {
1✔
163
    return this._message || "";
1!
164
  }
1✔
165

1✔
166
  get status(): TaskStatus {
1✔
167
    return this._status;
6,947✔
168
  }
6,947✔
169

1✔
170
  get isStarted(): boolean {
1✔
171
    return this.status !== "idle" && !this.isFinished;
817✔
172
  }
817✔
173

1✔
174
  get isFinished(): boolean {
1✔
175
    return (
1,630✔
176
      this.status === "fulfilled" ||
1,630✔
177
      this.status === "failed" ||
1,530✔
178
      this.status === "aborted"
1,493✔
179
    );
1,630✔
180
  }
1,630✔
181

1✔
182
  get isFailed(): boolean {
1✔
183
    return this.status === "failed";
127✔
184
  }
127✔
185

1✔
186
  get executeDuration(): number | undefined {
1✔
187
    return this._executeDuration;
×
188
  }
×
189

1✔
190
  get result(): any {
1✔
191
    return this._result;
52✔
192
  }
52✔
193

1✔
194
  get error(): any {
1✔
195
    return this._error;
125✔
196
  }
125✔
197

1✔
198
  get dependencies(): Task[] | undefined {
1✔
199
    return this._dependencies;
×
200
  }
×
201

1✔
202
  get failedChildren(): Task[] | undefined {
1✔
203
    return this._failedChildren;
×
204
  }
×
205

1✔
206
  get failedDependencies(): Task[] | undefined {
1✔
207
    return this._failedDependencies;
×
208
  }
×
209

1✔
210
  get needWaiting(): boolean {
1✔
211
    if (this._waitingFor && this._waitingFor.size) return true;
×
212
    if (this._children) {
×
213
      for (const c of this._children) {
×
214
        if (c.needWaiting) return true;
×
215
      }
×
216
    }
×
217
    return false;
×
218
  }
×
219

1✔
220
  getWaitingTasks(): Task[] | undefined {
1✔
221
    if (
168✔
222
      !(this.status === "waiting" && this._waitingFor && this._waitingFor.size)
168✔
223
    )
168✔
224
      return;
168✔
225
    const out = Array.from(this._waitingFor);
12✔
226
    if (this._children) {
168!
227
      for (const c of this._children) {
×
228
        const childTasks = c.getWaitingTasks();
×
229
        if (childTasks) {
×
230
          childTasks.forEach((t) => {
×
231
            if (!out.includes(t)) out.push(t);
×
232
          });
×
233
        }
×
234
      }
×
235
    }
✔
236
    return out;
12✔
237
  }
12✔
238

1✔
239
  abort(): this {
1✔
240
    if (this.isFinished || this.status === "aborting") return this;
38✔
241

22✔
242
    if (!this.isStarted) {
38✔
243
      this._update({ status: "aborted", message: "aborted" });
5✔
244
      return this;
5✔
245
    }
5✔
246

17✔
247
    const ctx = this[taskContextKey] as TaskContext;
17✔
248
    const timeout = this.options.abortTimeout || 30000;
38✔
249
    this._update({ status: "aborting", message: "Aborting" });
38✔
250
    if (timeout) {
38✔
251
      this._abortTimer = setTimeout(() => {
17✔
252
        delete this._abortTimer;
1✔
253
        this._update({ status: "aborted", message: "aborted" });
1✔
254
      }, timeout).unref();
17✔
255
    }
17✔
256
    this._abortChildren()
17✔
257
      .catch(noOp)
17✔
258
      .then(() => {
17✔
259
        if (this.isFinished) return;
17✔
260
        if (ctx.executingTasks.has(this)) {
17✔
261
          this._abortController.abort();
11✔
262
          return;
11✔
263
        }
11✔
264
        this._update({ status: "aborted", message: "aborted" });
5✔
265
      })
5✔
266
      .catch(noOp);
17✔
267
    return this;
17✔
268
  }
17✔
269

1✔
270
  start(): this {
1✔
271
    if (this.isStarted) return this;
39!
272
    this._id = this._id || "t" + ++idGen;
39✔
273
    const ctx = (this[taskContextKey] = new TaskContext());
39✔
274
    ctx.concurrency = this.options.concurrency || osCPUs;
39✔
275
    let pulseTimer: NodeJS.Timer | undefined;
39✔
276
    ctx.triggerPulse = () => {
39✔
277
      if (pulseTimer || this.isFinished) return;
93✔
278
      pulseTimer = setTimeout(() => {
28✔
279
        pulseTimer = undefined;
28✔
280
        this._pulse();
28✔
281
      }, 1);
28✔
282
    };
28✔
283
    if (this.options.children) {
39✔
284
      this._determineChildrenTree((err) => {
14✔
285
        if (err) {
14!
286
          this._update({
×
287
            status: "failed",
×
288
            error: err,
×
289
            message: "Unable to fetch child tasks. " + (err.message || err),
×
290
          });
×
291
          return;
×
292
        }
×
293
        this._determineChildrenDependencies([]);
14✔
294
        this._start();
14✔
295
      });
14✔
296
    } else this._start();
39✔
297
    return this;
38✔
298
  }
38✔
299

1✔
300
  toPromise(): Promise<T> {
1✔
301
    return new Promise((resolve, reject) => {
51✔
302
      if (this.isFinished) {
51✔
303
        if (this.isFailed) reject(this.error);
3!
304
        else resolve(this.result);
3✔
305
        return;
3✔
306
      }
3✔
307
      this.once("finish", () => {
48✔
308
        if (this.isFailed) return reject(this.error);
47✔
309
        resolve(this.result);
41✔
310
      });
41✔
311
      if (!this.isStarted && !this._isManaged) this.start();
51✔
312
    });
51✔
313
  }
51✔
314

1✔
315
  protected _determineChildrenTree(callback: (err?: any) => void): void {
1✔
316
    const ctx = this[taskContextKey] as TaskContext;
15✔
317
    const options = this._options;
15✔
318
    const handler = (err?: any, value?: any) => {
15✔
319
      if (err) return callback(err);
19!
320
      if (!value) return callback();
19!
321

19✔
322
      if (typeof value === "function") {
19✔
323
        try {
2✔
324
          const x: any = value();
2✔
325
          handler(undefined, x);
2✔
326
        } catch (err2) {
2!
327
          handler(err2);
×
328
        }
×
329
        return;
2✔
330
      }
2✔
331

17✔
332
      if (Array.isArray(value)) {
19✔
333
        let idx = 1;
15✔
334
        const children = value.reduce<Task[]>((a, v) => {
15✔
335
          // noinspection SuspiciousTypeOfGuard
58✔
336
          if (typeof v === "function") {
58✔
337
            v = new Task(v, {
5✔
338
              concurrency: options.concurrency,
5✔
339
              bail: options.bail,
5✔
340
            });
5✔
341
          }
5✔
342
          if (v instanceof Task) {
58✔
343
            v[taskContextKey] = ctx;
58✔
344
            v._id = v._id || this._id + "-" + idx++;
58✔
345
            const listeners = this.listeners("update-recursive");
58✔
346
            listeners.forEach((listener) =>
58✔
347
              v.on("update-recursive", listener as any),
55✔
348
            );
58✔
349
            a.push(v);
58✔
350
          }
58✔
351
          return a;
58✔
352
        }, []);
15✔
353

15✔
354
        if (children && children.length) {
15✔
355
          this._children = children;
15✔
356
          let i = 0;
15✔
357
          const next = (err2?: any) => {
15✔
358
            if (err2) return callback(err2);
73!
359
            if (i >= children.length) return callback();
73✔
360
            const c = children[i++];
58✔
361
            if (c.options.children)
58✔
362
              c._determineChildrenTree((err3) => next(err3));
73✔
363
            else next();
57✔
364
          };
73✔
365
          next();
15✔
366
        } else callback();
15!
367
        return;
14✔
368
      }
14✔
369
      if (value && typeof value.then === "function") {
19✔
370
        (value as Promise<TaskLike[]>)
2✔
371
          .then((v) => handler(undefined, v))
2✔
372
          .catch((e) => handler(e));
2✔
373
        return;
2✔
374
      }
2!
375

×
376
      callback(new Error("Invalid value returned from children() method."));
×
377
    };
×
378
    handler(undefined, this._options.children);
15✔
379
  }
15✔
380

1✔
381
  protected _determineChildrenDependencies(scope: Task[]): void {
1✔
382
    if (!this._children) return;
72✔
383

15✔
384
    const detectCircular = (
15✔
385
      t: Task,
20✔
386
      dependencies: Task[],
20✔
387
      path: string = "",
20✔
388
      list?: Set<Task>,
20✔
389
    ) => {
20✔
390
      path = path || t.name || t.id;
20!
391
      list = list || new Set();
20✔
392
      for (const l1 of dependencies.values()) {
20✔
393
        if (l1 === t) throw new Error(`Circular dependency detected. ${path}`);
20✔
394
        if (list.has(l1)) continue;
20!
395
        list.add(l1);
19✔
396
        if (l1._dependencies)
19✔
397
          detectCircular(
20✔
398
            t,
5✔
399
            l1._dependencies,
5✔
400
            path + " > " + (l1.name || l1.id),
5!
401
            list,
5✔
402
          );
17✔
403

17✔
404
        if (l1.children) {
20!
405
          for (const c of l1.children) {
×
406
            if (c === t)
×
407
              throw new Error(`Circular dependency detected. ${path}`);
×
408
            if (list.has(c)) continue;
×
409
            list.add(c);
×
410
            if (c._dependencies) detectCircular(t, c._dependencies, path, list);
×
411
          }
×
412
        }
×
413
      }
20✔
414
    };
17✔
415

15✔
416
    const subScope = [...scope, ...Array.from(this._children)];
15✔
417
    for (const c of this._children.values()) {
72✔
418
      c._determineChildrenDependencies(subScope);
58✔
419
      if (!c.options.dependencies) continue;
58✔
420

15✔
421
      const dependencies: Task[] = [];
15✔
422
      const waitingFor = new Set<Task>();
15✔
423
      for (const dep of c.options.dependencies) {
15✔
424
        const dependentTask = subScope.find((x) =>
15✔
425
          typeof dep === "string" ? x.name === dep : x === dep,
48✔
426
        );
15✔
427
        if (!dependentTask || c === dependentTask) continue;
15!
428
        dependencies.push(dependentTask);
15✔
429
        if (!dependentTask.isFinished) waitingFor.add(dependentTask);
15✔
430
      }
15✔
431
      detectCircular(c, dependencies);
15✔
432
      if (dependencies.length) c._dependencies = dependencies;
58✔
433
      if (waitingFor.size) c._waitingFor = waitingFor;
14✔
434
      c._captureDependencies();
14✔
435
    }
14✔
436
  }
14✔
437

1✔
438
  protected _captureDependencies(): void {
1✔
439
    if (!this._waitingFor) return;
14!
440
    const failedDependencies: Task[] = [];
14✔
441
    const waitingFor = this._waitingFor;
14✔
442
    const signal = this._abortController.signal;
14✔
443

14✔
444
    const abortSignalCallback = () => clearWait();
14✔
445
    signal.addEventListener("abort", abortSignalCallback, { once: true });
14✔
446

14✔
447
    const handleDependentAborted = () => {
14✔
448
      signal.removeEventListener("abort", abortSignalCallback);
×
449
      this._abortChildren()
×
450
        .then(() => {
×
451
          const isFailed = !!failedDependencies.find(
×
452
            (d) => d.status === "failed",
×
453
          );
×
454
          const error: any = new Error(
×
455
            "Aborted due to " +
×
456
              (isFailed ? "fail" : "cancellation") +
×
457
              " of dependent " +
×
458
              plural("task", !!failedDependencies.length),
×
459
          );
×
460
          error.failedDependencies = failedDependencies;
×
461
          this._failedDependencies = failedDependencies;
×
462
          this._update({
×
463
            status: isFailed ? "failed" : "aborted",
×
464
            message: error.message,
×
465
            error,
×
466
          });
×
467
        })
×
468
        .catch(noOp);
×
469
    };
×
470

14✔
471
    const clearWait = () => {
14✔
472
      for (const t of waitingFor) {
4✔
473
        t.removeListener("finish", finishCallback);
4✔
474
      }
4✔
475
      delete this._waitingFor;
4✔
476
    };
4✔
477

14✔
478
    const finishCallback = async (t) => {
14✔
479
      if (this.isStarted && this.status !== "waiting") {
12✔
480
        clearWait();
4✔
481
        return;
4✔
482
      }
4✔
483
      waitingFor.delete(t);
8✔
484
      if (t.isFailed || t.status === "aborted") {
12!
485
        failedDependencies.push(t);
×
486
      }
✔
487

8✔
488
      // If all dependent tasks completed
8✔
489
      if (!waitingFor.size) {
8✔
490
        delete this._waitingFor;
8✔
491
        signal.removeEventListener("abort", abortSignalCallback);
8✔
492

8✔
493
        // If any of dependent tasks are failed
8✔
494
        if (failedDependencies.length) {
8!
495
          handleDependentAborted();
×
496
          return;
×
497
        }
×
498
        // If all dependent tasks completed successfully we continue to next step (startChildren)
8✔
499
        if (this.isStarted) this._startChildren();
8✔
500
        else await this.emitAsync("wait-end");
1✔
501
      }
8✔
502
    };
12✔
503

14✔
504
    for (const t of waitingFor.values()) {
14✔
505
      if (t.isFailed || t.status === "aborted") {
14!
506
        waitingFor.delete(t);
×
507
        failedDependencies.push(t);
×
508
      } else t.prependOnceListener("finish", finishCallback);
14✔
509
    }
14✔
510
    if (!waitingFor.size) handleDependentAborted();
14!
511
  }
14✔
512

1✔
513
  protected _start(): void {
1✔
514
    if (this.isStarted || this.isFinished) return;
91!
515

91✔
516
    if (this._waitingFor) {
91✔
517
      this._update({
12✔
518
        status: "waiting",
12✔
519
        message: "Waiting for dependencies",
12✔
520
        waitingFor: true,
12✔
521
      });
12✔
522
      return;
12✔
523
    }
12✔
524
    this._startChildren();
79✔
525
  }
79✔
526

1✔
527
  protected _startChildren() {
1✔
528
    const children = this._children;
86✔
529
    if (!children) {
86✔
530
      this._pulse();
72✔
531
      return;
72✔
532
    }
72✔
533

14✔
534
    const options = this.options;
14✔
535
    const childrenLeft = (this._childrenLeft = new Set(children));
14✔
536
    const failedChildren: Task[] = [];
14✔
537

14✔
538
    const statusChangeCallback = async (t: Task) => {
14✔
539
      if (this.status === "aborting") return;
128✔
540
      if (t.status === "running")
104✔
541
        this._update({ status: "running", message: "Running" });
128✔
542
      if (t.status === "waiting")
104✔
543
        this._update({ status: "waiting", message: "Waiting" });
128✔
544
    };
128✔
545

14✔
546
    const finishCallback = async (t: Task) => {
14✔
547
      t.removeListener("status-change", statusChangeCallback);
55✔
548
      childrenLeft.delete(t);
55✔
549
      if (t.isFailed || t.status === "aborted") {
55✔
550
        failedChildren.push(t);
19✔
551
        if (options.bail && childrenLeft.size) {
19✔
552
          const running = !!children.find((c) => c.isStarted);
13✔
553
          if (running)
13✔
554
            this._update({ status: "aborting", message: "Aborting" });
13✔
555
          this._abortChildren().catch(noOp);
13✔
556
          return;
13✔
557
        }
13✔
558
      }
19✔
559

42✔
560
      if (!childrenLeft.size) {
55✔
561
        delete this._childrenLeft;
14✔
562
        if (failedChildren.length) {
14✔
563
          const isFailed = !!failedChildren.find((d) => d.status === "failed");
7✔
564
          const error: any = new Error(
7✔
565
            "Aborted due to " +
7✔
566
              (isFailed ? "fail" : "cancellation") +
7✔
567
              " of child " +
7✔
568
              plural("task", !!failedChildren.length),
7✔
569
          );
7✔
570
          error.failedChildren = failedChildren;
7✔
571
          this._failedChildren = failedChildren;
7✔
572
          this._update({
7✔
573
            status: isFailed ? "failed" : "aborted",
7✔
574
            error,
7✔
575
            message: error.message,
7✔
576
          });
7✔
577
          return;
7✔
578
        }
7✔
579
      }
14✔
580
      this._pulse();
35✔
581
    };
35✔
582

14✔
583
    for (const c of children) {
86✔
584
      c.prependOnceListener("wait-end", () => this._pulse());
55✔
585
      c.prependOnceListener("finish", finishCallback);
55✔
586
      c.prependListener("status-change", statusChangeCallback);
55✔
587
    }
55✔
588

14✔
589
    this._pulse();
14✔
590
  }
14✔
591

1✔
592
  protected _pulse() {
1✔
593
    const ctx = this[taskContextKey] as TaskContext;
223✔
594

223✔
595
    if (
223✔
596
      this.isFinished ||
223✔
597
      this._waitingFor ||
223✔
598
      this.status === "aborting" ||
184✔
599
      ctx.executingTasks.has(this)
181✔
600
    )
223✔
601
      return;
223✔
602

139✔
603
    const options = this.options;
139✔
604
    if (this._childrenLeft) {
223✔
605
      // Check if we can run multiple child tasks
60✔
606
      for (const c of this._childrenLeft) {
60✔
607
        if (
169✔
608
          (c.isStarted && options.serial) ||
169✔
609
          (c.status === "running" && c.options.exclusive)
169✔
610
        ) {
169✔
611
          c._pulse();
4✔
612
          return;
4✔
613
        }
4✔
614
      }
169✔
615

56✔
616
      // Check waiting children
56✔
617
      let hasExclusive = false;
56✔
618
      let hasRunning = false;
56✔
619
      for (const c of this._childrenLeft) {
60✔
620
        if (c.isFinished) continue;
165!
621
        hasExclusive = hasExclusive || !!c.options.exclusive;
165✔
622
        hasRunning = hasRunning || c.status === "running";
165✔
623
      }
165✔
624
      if (hasExclusive && hasRunning) return;
60!
625

56✔
626
      // start children
56✔
627
      let k = ctx.concurrency - ctx.executingTasks.size;
56✔
628
      for (const c of this._childrenLeft) {
60✔
629
        if (c.isStarted) {
131✔
630
          c._pulse();
69✔
631
          continue;
69✔
632
        }
69✔
633
        if (k-- <= 0) return;
131✔
634
        if (
54✔
635
          c.options.exclusive &&
54✔
636
          (ctx.executingTasks.size || ctx.executingTasks.size)
2✔
637
        )
131✔
638
          return;
131✔
639
        c._start();
53✔
640
        if (options.serial || (c.status === "running" && c.options.exclusive))
131✔
641
          return;
131✔
642
      }
131✔
643
    }
38✔
644

117✔
645
    if (
117✔
646
      (this._childrenLeft && this._childrenLeft.size) ||
223✔
647
      ctx.executingTasks.size >= ctx.concurrency
79✔
648
    )
223✔
649
      return;
223✔
650

79✔
651
    this._update({ status: "running", message: "Running" });
79✔
652
    ctx.executingTasks.add(this);
79✔
653
    const t = Date.now();
79✔
654
    const signal = this._abortController.signal;
79✔
655
    (async () =>
79✔
656
      (this._executeFn || noOp)({
79✔
657
        task: this,
79✔
658
        signal,
79✔
659
      }))()
79✔
660
      .then((result: any) => {
79✔
661
        ctx.executingTasks.delete(this);
63✔
662
        this._executeDuration = Date.now() - t;
63✔
663
        this._update({
63✔
664
          status: "fulfilled",
63✔
665
          message: "Task completed",
63✔
666
          result,
63✔
667
        });
63✔
668
      })
63✔
669
      .catch((error) => {
79✔
670
        ctx.executingTasks.delete(this);
16✔
671
        this._executeDuration = Date.now() - t;
16✔
672
        if (error.code === "ABORT_ERR") {
16✔
673
          this._update({
8✔
674
            status: "aborted",
8✔
675
            error,
8✔
676
            message: error instanceof Error ? error.message : "" + error,
8!
677
          });
8✔
678
          return;
8✔
679
        }
8✔
680
        this._update({
8✔
681
          status: "failed",
8✔
682
          error,
8✔
683
          message: error instanceof Error ? error.message : "" + error,
16!
684
        });
16✔
685
      });
16✔
686
  }
79✔
687

1✔
688
  protected _update(prop: TaskUpdateValues) {
1✔
689
    const oldFinished = this.isFinished;
276✔
690
    const keys: string[] = [];
276✔
691
    const oldStarted = this.isStarted;
276✔
692
    if (prop.status && this._status !== prop.status) {
276✔
693
      this._status = prop.status;
218✔
694
      keys.push("status");
218✔
695
    }
218✔
696
    if (prop.message && this._message !== prop.message) {
276✔
697
      this._message = prop.message;
210✔
698
      keys.push("message");
210✔
699
    }
210✔
700
    if (prop.error && this._error !== prop.error) {
276✔
701
      this._error = prop.error;
23✔
702
      keys.push("error");
23✔
703
    }
23✔
704
    if (prop.result && this._result !== prop.result) {
276✔
705
      this._result = prop.result;
15✔
706
      keys.push("result");
15✔
707
    }
15✔
708
    if (prop.waitingFor) {
276✔
709
      keys.push("waitingFor");
12✔
710
    }
12✔
711
    if (keys.length) {
276✔
712
      if (keys.includes("status")) {
218✔
713
        if (!oldStarted) this.emitAsync("start", this).catch(noOp);
218✔
714
        this.emitAsync("status-change", this).catch(noOp);
218✔
715
        if (this._status === "running") this.emitAsync("run", this).catch(noOp);
218✔
716
      }
218✔
717
      this.emitAsync("update", this, keys).catch(noOp);
218✔
718
      this.emitAsync("update-recursive", this, keys).catch(noOp);
218✔
719
      if (this.isFinished && !oldFinished) {
218✔
720
        const ctx = this[taskContextKey];
96✔
721
        if (this._abortTimer) {
96✔
722
          clearTimeout(this._abortTimer);
16✔
723
          delete this._abortTimer;
16✔
724
        }
16✔
725
        delete this[taskContextKey];
96✔
726
        if (this.error) this.emitAsync("error", this.error).catch(noOp);
96✔
727
        this.emitAsync("finish", this).catch(noOp);
96✔
728
        if (ctx) ctx.triggerPulse();
96✔
729
      }
96✔
730
    }
218✔
731
  }
276✔
732

1✔
733
  protected async _abortChildren(): Promise<void> {
1✔
734
    const promises: Promise<void>[] = [];
30✔
735
    if (this._children) {
30✔
736
      for (let i = this._children.length - 1; i >= 0; i--) {
14✔
737
        const child = this._children[i];
62✔
738
        if (!child.isFinished) {
62✔
739
          child.abort();
29✔
740
          promises.push(child.toPromise());
29✔
741
        }
29✔
742
      }
62✔
743
    }
14✔
744
    if (promises.length) await Promise.all(promises);
30✔
745
  }
30✔
746
}
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc