11import {
22 cancelAnimationFrame ,
33 clearTimeout ,
4+ nativePostTask ,
5+ NOOP ,
46 requestAnimationFrame ,
57 setTimeout ,
68 TELEMETRY_FREQUENCY_30PS ,
@@ -80,81 +82,106 @@ export class Stopper {
8082 }
8183}
8284
83- interface ITimerOptions {
84- /** a delay of setTimeout or setInterval (default: 0); irrelevant if `animation` is true */
85- delay ?: number ;
86- /** act as setInterval by repeating setTimeout (default: false) */
87- repetitive ?: boolean ;
88- /** act as requestAnimationFrame called from another requestAnimationFrame (default: false);
89- if true - `delay` is redundant */
90- animation ?: boolean ;
85+ /** @link : https://developer.mozilla.org/en-US/docs/Web/API/Prioritized_Task_Scheduling_API#task_priorities */
86+ export type TTaskPriority =
87+ | 'user-blocking'
88+ | 'user-visible' // default
89+ | 'background' ;
90+ export enum ETimer {
91+ TIMEOUT ,
92+ ANIMATION ,
93+ TASK ,
94+ }
95+ type TTimerMeasurable = {
9196 /** populate `callbackSelfTime` with measured execution time of `callback` (default: false) */
9297 measurable ?: boolean ;
93- }
98+ } ;
99+ type TTimerTimeout = TTimerMeasurable & {
100+ type : ETimer . TIMEOUT ;
101+ delay : number ;
102+ } ;
103+ type TTimerAnimation = TTimerMeasurable & {
104+ type : ETimer . ANIMATION ;
105+ } ;
106+ type TTimerTask = TTimerMeasurable & {
107+ type : ETimer . TASK ;
108+ delay : number ;
109+ priority ?: TTaskPriority ;
110+ } ;
111+ type TTimerOptions =
112+ | TTimerTimeout
113+ | TTimerAnimation
114+ | TTimerTask ;
94115
95116/**
96- * A unification of ways to delay a callback to another time in javascript event-loop
97- * - `repetitive: false` - will call `setTimeout` with constant `delay`.
98- * - `repetitive: true` - will call `setTimeout` but act as `setInterval` with changeable `delay`.
99- * - `animation: true` - will call `requestAnimationFrame` in recursive way (means to follow the browser's frame-rate).
100- * - `measurable: true` - measure the callback's execution time.
117+ * A unification of ways to delay a callback execution
118+ * in javascript event-loop
101119 */
102120export class Timer {
103121 delay : number = 0 ;
104122 /** callback's self-time in milliseconds */
105123 callbackSelfTime : number = - 1 ;
106124 #handler: number = 0 ;
125+ #abortController: AbortController | null = null ;
107126 readonly #fn: ( ...args : unknown [ ] ) => void ;
108127 readonly #stopper?: Stopper ;
109- readonly #options: ITimerOptions ;
110- static readonly DEFAULT_OPTIONS : ITimerOptions = {
111- delay : 0 ,
112- repetitive : false ,
113- animation : false ,
114- measurable : false ,
115- } ;
128+ readonly #options: TTimerOptions ;
116129
117- constructor ( o : ITimerOptions , fn : ( ...args : unknown [ ] ) => void ) {
118- this . #options = Object . assign ( { } , Timer . DEFAULT_OPTIONS , o ) ;
130+ constructor ( o : TTimerOptions , fn : ( ...args : unknown [ ] ) => void ) {
131+ this . #options = Object . assign ( { } , o ) ;
119132 this . #fn = fn ;
120- this . delay = this . #options. delay || 0 ;
133+
134+ if (
135+ this . #options. type === ETimer . TIMEOUT ||
136+ this . #options. type === ETimer . TASK
137+ ) {
138+ this . delay = this . #options. delay ;
139+ }
121140
122141 if ( this . #options. measurable ) {
123142 this . #stopper = new Stopper ( ) ;
124143 }
125144 }
126145
127146 start ( ...args : unknown [ ] ) {
128- if ( this . #handler ) {
147+ if ( this . isPending ( ) ) {
129148 this . stop ( ) ;
130149 }
131150
132- if ( this . #options. animation ) {
133- this . #handler = requestAnimationFrame ( ( ) => {
151+ if (
152+ this . #options. type === ETimer . TIMEOUT
153+ ) {
154+ this . #handler = setTimeout ( ( ) => {
155+ this . #handler = 0 ;
134156 this . trigger ( ...args ) ;
157+ } , this . delay ) ;
158+ } else if (
159+ this . #options. type === ETimer . ANIMATION
160+ ) {
161+ this . #handler = requestAnimationFrame ( ( ) => {
135162 this . #handler = 0 ;
136-
137- if ( this . #options. repetitive ) {
138- this . start ( ...args ) ;
139- }
163+ this . trigger ( ...args ) ;
140164 } ) ;
141- } else {
142- this . #handler = setTimeout ( ( ) => {
165+ } else if ( this . #options. type === ETimer . TASK ) {
166+ this . #abortController = new AbortController ( ) ;
167+ nativePostTask ( ( ) => {
168+ this . #abortController = null ;
143169 this . trigger ( ...args ) ;
144- this . #handler = 0 ;
145-
146- if ( this . #options. repetitive ) {
147- this . start ( ...args ) ;
148- }
149- } , this . delay ) ;
170+ } , {
171+ delay : this . delay ,
172+ signal : this . #abortController. signal ,
173+ priority : this . #options. priority ,
174+ } ) . catch ( NOOP ) ;
150175 }
151176
152177 return this ;
153178 }
154179
155180 trigger ( ...args : unknown [ ] ) {
156181 this . #stopper?. start ( ) ;
182+
157183 this . #fn( ...args ) ;
184+
158185 if ( this . #stopper) {
159186 this . callbackSelfTime = this . #stopper. stop ( ) . value ( ) ;
160187 }
@@ -163,21 +190,30 @@ export class Timer {
163190 }
164191
165192 stop ( ) {
166- if ( this . #handler) {
167- if ( this . #options. animation ) {
168- cancelAnimationFrame ( this . #handler) ;
169- } else {
170- clearTimeout ( this . #handler) ;
171- }
172-
193+ if ( this . #options. type === ETimer . TIMEOUT ) {
194+ this . #handler && clearTimeout ( this . #handler) ;
195+ this . #handler = 0 ;
196+ } else if ( this . #options. type === ETimer . ANIMATION ) {
197+ this . #handler && cancelAnimationFrame ( this . #handler) ;
173198 this . #handler = 0 ;
199+ } else if ( this . #options. type === ETimer . TASK ) {
200+ this . #abortController && this . #abortController. abort ( ) ;
201+ this . #abortController = null ;
174202 }
175203
176204 return this ;
177205 }
178206
179- /** Timer status: true | false => Pending | unstarted/finished/stopped */
180- isPending ( ) {
207+ /**
208+ * Timer status:
209+ * true := scheduled, pending execution
210+ * false := unstarted or finished
211+ */
212+ isPending ( ) : boolean {
213+ if ( this . #options. type === ETimer . TASK ) {
214+ return ! ! this . #abortController;
215+ }
216+
181217 return this . #handler !== 0 ;
182218 }
183219}
@@ -190,20 +226,21 @@ export class Fps {
190226 /** registered number of calls */
191227 value = 0 ;
192228 #ticks = 0 ;
193- #interval : Timer ;
229+ #eachSecond : Timer ;
194230
195231 constructor ( callback ?: ( value : number ) => void ) {
196- this . #interval = new Timer ( { delay : 1e3 , repetitive : true } , ( ) => {
232+ this . #eachSecond = new Timer ( { type : ETimer . TIMEOUT , delay : 1e3 } , ( ) => {
197233 this . value = this . #ticks;
198234 this . #ticks = 0 ;
199235 callback ?.( this . value ) ;
236+ this . #eachSecond. start ( ) ;
200237 } ) ;
201238 }
202239
203240 start ( ) {
204241 this . #ticks = 0 ;
205242 this . value = 0 ;
206- this . #interval . start ( ) ;
243+ this . #eachSecond . start ( ) ;
207244 return this ;
208245 }
209246
@@ -213,7 +250,7 @@ export class Fps {
213250 }
214251
215252 stop ( ) {
216- this . #interval . stop ( ) ;
253+ this . #eachSecond . stop ( ) ;
217254 return this ;
218255 }
219256}
0 commit comments