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

rokucommunity / brighterscript / #15085

13 Jan 2026 06:34PM UTC coverage: 87.292% (-0.01%) from 87.304%
#15085

push

web-flow
Merge 86699e23e into bb7e98e1c

14410 of 17441 branches covered (82.62%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

3 existing lines in 2 files now uncovered.

15093 of 16357 relevant lines covered (92.27%)

24231.24 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>> = [];
189✔
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();
128✔
28

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

36
        void this._run(runInfo, () => {
128✔
37
            //don't mark the busy run as completed until the deferred is resolved
38
            return deferred.promise;
128✔
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);
254✔
49
        if (earliestRunIndex === -1) {
122!
UNCOV
50
            return;
×
51
        }
52
        const earliestRun = this.activeRuns[earliestRunIndex];
122✔
53
        this.activeRuns.splice(earliestRunIndex, 1);
122✔
54
        earliestRun.deferred.resolve();
122✔
55
        return earliestRun.deferred.promise;
122✔
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--) {
15✔
64
            const activeRun = this.activeRuns[i];
9✔
65
            if (activeRun.scope === scope) {
9✔
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 = {
374✔
79
            label: label,
80
            startTime: new Date()
81
        };
82
        return this._run<A, R>(run, callback);
374✔
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);
502✔
87

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

93
        let isFinalized = false;
502✔
94
        const finalizeRun = () => {
502✔
95
            if (isFinalized === false) {
501✔
96
                isFinalized = true;
499✔
97

98
                let idx = this.activeRuns.indexOf(runInfo);
499✔
99
                if (idx > -1) {
499✔
100
                    this.activeRuns.splice(idx, 1);
374✔
101
                }
102

103
                this.emit('active-runs-change', { activeRuns: [...this.activeRuns] });
499✔
104

105
                if (this.activeRuns.length <= 0) {
499✔
106
                    this.emit('change', BusyStatus.idle);
249✔
107
                }
108
            }
109
        };
110

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

128
    private emitter = new EventEmitter<string, BusyStatus>();
189✔
129

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

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

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

156
    public destroy() {
157
        this.emitter.removeAllListeners();
15✔
158
    }
159

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

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

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