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

rokucommunity / brighterscript / #13274

04 Nov 2024 02:42PM UTC coverage: 89.088% (+0.9%) from 88.207%
#13274

push

web-flow
Merge 99d15489d into d2d08a75d

7343 of 8689 branches covered (84.51%)

Branch coverage included in aggregate %.

1105 of 1221 new or added lines in 28 files covered. (90.5%)

24 existing lines in 5 files now uncovered.

9688 of 10428 relevant lines covered (92.9%)

1797.47 hits per line

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

97.56
/src/BusyStatusTracker.ts
1
import { EventEmitter } from 'eventemitter3';
1✔
2
import { Deferred } from './deferred';
1✔
3

4
interface RunInfo<T> {
5
    label: string;
6
    startTime: Date;
7
    scope?: T;
8
    deferred?: Deferred;
9
}
10

11
/**
12
 * Tracks the busy/idle status of various sync or async tasks
13
 * Reports the overall status to the client
14
 */
15
export class BusyStatusTracker<T = any> {
1✔
16
    /**
17
     * @readonly
18
     */
19
    public activeRuns: Array<RunInfo<T>> = [];
155✔
20

21
    /**
22
     * Begin a busy task. It's expected you will call `endScopeRun` when the task is complete.
23
     * @param scope an object used for reference as to what is doing the work. Can be used to bulk-cancel all runs for a given scope.
24
     * @param label  label for the run. This is required for the `endScopedRun` method to know what to end.
25
     */
26
    public beginScopedRun(scope: T, label: string) {
27
        const deferred = new Deferred();
97✔
28

29
        const runInfo = {
97✔
30
            label: label,
31
            startTime: new Date(),
32
            deferred: deferred,
33
            scope: scope
34
        };
35

36
        void this._run(runInfo, () => {
97✔
37
            //don't mark the busy run as completed until the deferred is resolved
38
            return deferred.promise;
97✔
39
        });
40
    }
41

42
    /**
43
     * End the earliest run for the given scope and label
44
     * @param scope an object used for reference as to what is doing the work. Can be used to bulk-cancel all runs for a given scope.
45
     * @param label label for the run
46
     */
47
    public endScopedRun(scope: T, label: string) {
48
        const earliestRunIndex = this.activeRuns.findIndex(x => x.scope === scope && x.label === label);
128✔
49
        if (earliestRunIndex === -1) {
91!
NEW
50
            return;
×
51
        }
52
        const earliestRun = this.activeRuns[earliestRunIndex];
91✔
53
        this.activeRuns.splice(earliestRunIndex, 1);
91✔
54
        earliestRun.deferred.resolve();
91✔
55
        return earliestRun.deferred.promise;
91✔
56
    }
57

58
    /**
59
     * End all runs for a given scope. This is typically used when the scope is destroyed, and we want to make sure all runs are cleaned up.
60
     * @param scope an object used for reference as to what is doing the work.
61
     */
62
    public endAllRunsForScope(scope: T) {
63
        for (let i = this.activeRuns.length - 1; i >= 0; i--) {
14✔
64
            const activeRun = this.activeRuns[i];
10✔
65
            if (activeRun.scope === scope) {
10✔
66
                //delete the active run
67
                this.activeRuns.splice(i, 1);
3✔
68
                //mark the run as resolved
69
                activeRun.deferred.resolve();
3✔
70
            }
71
        }
72
    }
73

74
    /**
75
     * Start a new piece of work
76
     */
77
    public run<A, R = A | Promise<A>>(callback: (finalize?: FinalizeBuildStatusRun) => R, label?: string): R {
78
        const run = {
289✔
79
            label: label,
80
            startTime: new Date()
81
        };
82
        return this._run<A, R>(run, callback);
289✔
83
    }
84

85
    private _run<A, R = A | Promise<A>>(runInfo: RunInfo<T>, callback: (finalize?: FinalizeBuildStatusRun) => R, label?: string): R {
86
        this.activeRuns.push(runInfo);
386✔
87

88
        if (this.activeRuns.length === 1) {
386✔
89
            this.emit('change', BusyStatus.busy);
219✔
90
        }
91
        this.emit('active-runs-change', { activeRuns: [...this.activeRuns] });
386✔
92

93
        let isFinalized = false;
386✔
94
        const finalizeRun = () => {
386✔
95
            if (isFinalized === false) {
385✔
96
                isFinalized = true;
383✔
97

98
                this.emit('active-runs-change', { activeRuns: [...this.activeRuns] });
383✔
99

100
                let idx = this.activeRuns.indexOf(runInfo);
383✔
101
                if (idx > -1) {
383✔
102
                    this.activeRuns.splice(idx, 1);
289✔
103
                }
104
                if (this.activeRuns.length <= 0) {
383✔
105
                    this.emit('change', BusyStatus.idle);
219✔
106
                }
107
            }
108
        };
109

110
        let result: R | PromiseLike<R>;
111
        //call the callback function
112
        try {
386✔
113
            result = callback(finalizeRun);
386✔
114
            //if the result is a promise, don't finalize until it completes
115
            if (typeof (result as any)?.then === 'function') {
385✔
116
                return Promise.resolve(result).finally(finalizeRun).then(() => result) as any;
374✔
117
            } else {
118
                finalizeRun();
11✔
119
                return result;
11✔
120
            }
121
        } catch (e) {
122
            finalizeRun();
1✔
123
            throw e;
1✔
124
        }
125
    }
126

127
    private emitter = new EventEmitter<string, BusyStatus>();
155✔
128

129
    public once(eventName: 'active-runs-change'): Promise<{ activeRuns: Array<RunInfo<T>> }>;
130
    public once(eventName: 'change'): Promise<BusyStatus>;
131
    public once<T>(eventName: string): Promise<T> {
132
        return new Promise<T>((resolve) => {
4✔
133
            const off = this.on(eventName as any, (data) => {
4✔
134
                off();
4✔
135
                resolve(data as any);
4✔
136
            });
137
        });
138
    }
139

140
    public on(eventName: 'active-runs-change', handler: (event: { activeRuns: Array<RunInfo<T>> }) => void);
141
    public on(eventName: 'change', handler: (status: BusyStatus) => void);
142
    public on(eventName: string, handler: (event: any) => void) {
143
        this.emitter.on(eventName, handler);
75✔
144
        return () => {
75✔
145
            this.emitter.off(eventName, handler);
5✔
146
        };
147
    }
148

149
    private emit(eventName: 'active-runs-change', event: { activeRuns: Array<RunInfo<T>> });
150
    private emit(eventName: 'change', status: BusyStatus);
151
    private emit(eventName: string, event: any) {
152
        this.emitter.emit(eventName, event);
1,207✔
153
    }
154

155
    public destroy() {
156
        this.emitter.removeAllListeners();
14✔
157
    }
158

159
    /**
160
     * The current status of the busy tracker.
161
     * @readonly
162
     */
163
    public get status() {
164
        return this.activeRuns.length === 0 ? BusyStatus.idle : BusyStatus.busy;
430✔
165
    }
166
}
167

168
export type FinalizeBuildStatusRun = (status?: BusyStatus) => void;
169

170
export enum BusyStatus {
1✔
171
    busy = 'busy',
1✔
172
    idle = 'idle'
1✔
173
}
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