1010 * @suppress {missingRequire}
1111 */
1212
13- import { patchMethod , scheduleMacroTaskWithCurrentZone , zoneSymbol } from './utils' ;
13+ import {
14+ isFunction ,
15+ isNumber ,
16+ patchMethod ,
17+ scheduleMacroTaskWithCurrentZone ,
18+ zoneSymbol ,
19+ } from './utils' ;
1420
15- const taskSymbol = zoneSymbol ( 'zoneTask' ) ;
21+ export const taskSymbol = zoneSymbol ( 'zoneTask' ) ;
1622
1723interface TimerOptions extends TaskData {
1824 handleId ?: number ;
@@ -32,25 +38,42 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam
3238 data . args [ 0 ] = function ( ) {
3339 return task . invoke . apply ( this , arguments ) ;
3440 } ;
35- data . handleId = setNative ! . apply ( window , data . args ) ;
41+
42+ const handleOrId = setNative ! . apply ( window , data . args ) ;
43+
44+ // Whlist on Node.js when get can the ID by using `[Symbol.toPrimitive]()` we do
45+ // to this so that we do not cause potentally leaks when using `setTimeout`
46+ // since this can be periodic when using `.refresh`.
47+ if ( isNumber ( handleOrId ) ) {
48+ data . handleId = handleOrId ;
49+ } else {
50+ data . handle = handleOrId ;
51+ // On Node.js a timeout and interval can be restarted over and over again by using the `.refresh` method.
52+ data . isRefreshable = isFunction ( handleOrId . refresh ) ;
53+ }
54+
3655 return task ;
3756 }
3857
3958 function clearTask ( task : Task ) {
40- return clearNative ! . call ( window , ( < TimerOptions > task . data ) . handleId ) ;
59+ const { handle, handleId} = task . data ! ;
60+
61+ return clearNative ! . call ( window , handle ?? handleId ) ;
4162 }
4263
4364 setNative = patchMethod (
4465 window ,
4566 setName ,
4667 ( delegate : Function ) =>
4768 function ( self : any , args : any [ ] ) {
48- if ( typeof args [ 0 ] === 'function' ) {
69+ if ( isFunction ( args [ 0 ] ) ) {
4970 const options : TimerOptions = {
71+ isRefreshable : false ,
5072 isPeriodic : nameSuffix === 'Interval' ,
5173 delay : nameSuffix === 'Timeout' || nameSuffix === 'Interval' ? args [ 1 ] || 0 : undefined ,
5274 args : args ,
5375 } ;
76+
5477 const callback = args [ 0 ] ;
5578 args [ 0 ] = function timer ( this : unknown ) {
5679 try {
@@ -64,15 +87,17 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam
6487 // Cleanup tasksByHandleId should be handled before scheduleTask
6588 // Since some zoneSpec may intercept and doesn't trigger
6689 // scheduleFn(scheduleTask) provided here.
67- if ( ! options . isPeriodic ) {
68- if ( typeof options . handleId === 'number' ) {
90+ const { handle, handleId, isPeriodic, isRefreshable} = options ;
91+
92+ if ( ! isPeriodic && ! isRefreshable ) {
93+ if ( handleId ) {
6994 // in non-nodejs env, we remove timerId
7095 // from local cache
71- delete tasksByHandleId [ options . handleId ] ;
72- } else if ( options . handleId ) {
96+ delete tasksByHandleId [ handleId ] ;
97+ } else if ( handle ) {
7398 // Node returns complex objects as handleIds
7499 // we remove task reference from timer object
75- ( options . handleId as any ) [ taskSymbol ] = null ;
100+ handle [ taskSymbol ] = null ;
76101 }
77102 }
78103 }
@@ -84,37 +109,39 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam
84109 scheduleTask ,
85110 clearTask ,
86111 ) ;
112+
87113 if ( ! task ) {
88114 return task ;
89115 }
116+
90117 // Node.js must additionally support the ref and unref functions.
91- const handle : any = ( < TimerOptions > task . data ) . handleId ;
92- if ( typeof handle === 'number' ) {
118+ const { handleId , handle, isRefreshable , isPeriodic } = < TimerOptions > task . data ;
119+ if ( handleId ) {
93120 // for non nodejs env, we save handleId: task
94121 // mapping in local cache for clearTimeout
95- tasksByHandleId [ handle ] = task ;
122+ tasksByHandleId [ handleId ] = task ;
96123 } else if ( handle ) {
97124 // for nodejs env, we save task
98125 // reference in timerId Object for clearTimeout
99126 handle [ taskSymbol ] = task ;
100- }
101127
102- // check whether handle is null, because some polyfill or browser
103- // may return undefined from setTimeout/setInterval/setImmediate/requestAnimationFrame
104- if (
105- handle &&
106- handle . ref &&
107- handle . unref &&
108- typeof handle . ref === 'function' &&
109- typeof handle . unref === 'function'
110- ) {
111- ( < any > task ) . ref = ( < any > handle ) . ref . bind ( handle ) ;
112- ( < any > task ) . unref = ( < any > handle ) . unref . bind ( handle ) ;
113- }
114- if ( typeof handle === 'number' || handle ) {
115- return handle ;
128+ if ( isRefreshable && ! isPeriodic ) {
129+ const originalRefresh = handle . refresh ;
130+ handle . refresh = function ( ) {
131+ const { zone , state } = task as any ;
132+ if ( state === 'notScheduled' ) {
133+ ( task as any ) . _state = 'scheduled' ;
134+ zone . _updateTaskCount ( task , 1 ) ;
135+ } else if ( state === 'running' ) {
136+ ( task as any ) . _state = 'scheduling' ;
137+ }
138+
139+ return originalRefresh . call ( this ) ;
140+ } ;
141+ }
116142 }
117- return task ;
143+
144+ return handle ?? handleId ?? task ;
118145 } else {
119146 // cause an error by calling it directly.
120147 return delegate . apply ( window , args ) ;
@@ -129,27 +156,23 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam
129156 function ( self : any , args : any [ ] ) {
130157 const id = args [ 0 ] ;
131158 let task : Task ;
132- if ( typeof id === 'number' ) {
159+
160+ if ( isNumber ( id ) ) {
133161 // non nodejs env.
134162 task = tasksByHandleId [ id ] ;
163+ delete tasksByHandleId [ id ] ;
135164 } else {
136- // nodejs env.
137- task = id && id [ taskSymbol ] ;
138- // other environments.
139- if ( ! task ) {
165+ // nodejs env ?? other environments.
166+ task = id ?. [ taskSymbol ] ;
167+ if ( task ) {
168+ id [ taskSymbol ] = null ;
169+ } else {
140170 task = id ;
141171 }
142172 }
143- if ( task && typeof task . type === 'string' ) {
144- if (
145- task . state !== 'notScheduled' &&
146- ( ( task . cancelFn && task . data ! . isPeriodic ) || task . runCount === 0 )
147- ) {
148- if ( typeof id === 'number' ) {
149- delete tasksByHandleId [ id ] ;
150- } else if ( id ) {
151- id [ taskSymbol ] = null ;
152- }
173+
174+ if ( task ?. type ) {
175+ if ( task . cancelFn ) {
153176 // Do not cancel already canceled functions
154177 task . zone . cancelTask ( task ) ;
155178 }
0 commit comments