|
1 | 1 | (function(global: any) { |
2 | 2 | interface ScheduledFunction { |
3 | 3 | endTime: number; |
4 | | - id: number, |
| 4 | + id: number; |
5 | 5 | func: Function; |
6 | 6 | args: any[]; |
7 | 7 | delay: number; |
8 | 8 | } |
9 | | - |
| 9 | + |
10 | 10 | class Scheduler { |
11 | 11 | // Next scheduler id. |
12 | 12 | public nextId: number = 0; |
13 | | - |
| 13 | + |
14 | 14 | // Scheduler queue with the tuple of end time and callback function - sorted by end time. |
15 | 15 | private _schedulerQueue: ScheduledFunction[] = []; |
16 | 16 | // Current simulated time in millis. |
17 | 17 | private _currentTime: number = 0; |
18 | | - |
| 18 | + |
19 | 19 | constructor() {} |
20 | | - |
| 20 | + |
21 | 21 | scheduleFunction(cb: Function, delay: number, args: any[] = [], id: number = -1) : number { |
22 | 22 | let currentId: number = id < 0 ? this.nextId++ : id; |
23 | 23 | let endTime = this._currentTime + delay; |
24 | | - |
| 24 | + |
25 | 25 | // Insert so that scheduler queue remains sorted by end time. |
26 | 26 | let newEntry: ScheduledFunction = { |
27 | 27 | endTime: endTime, |
28 | 28 | id: currentId, |
29 | 29 | func: cb, |
30 | 30 | args: args, |
31 | 31 | delay: delay |
32 | | - } |
| 32 | + }; |
33 | 33 | let i = 0; |
34 | 34 | for (; i < this._schedulerQueue.length; i++) { |
35 | 35 | let currentEntry = this._schedulerQueue[i]; |
|
40 | 40 | this._schedulerQueue.splice(i, 0, newEntry); |
41 | 41 | return currentId; |
42 | 42 | } |
43 | | - |
| 43 | + |
44 | 44 | removeScheduledFunctionWithId(id: number): void { |
45 | 45 | for (let i = 0; i < this._schedulerQueue.length; i++) { |
46 | 46 | if (this._schedulerQueue[i].id == id) { |
|
49 | 49 | } |
50 | 50 | } |
51 | 51 | } |
52 | | - |
| 52 | + |
53 | 53 | tick(millis: number = 0): void { |
54 | 54 | let finalTime = this._currentTime + millis; |
55 | 55 | while (this._schedulerQueue.length > 0) { |
56 | | - let current = this._schedulerQueue[0]; |
| 56 | + let current = this._schedulerQueue[0]; |
57 | 57 | if (finalTime < current.endTime) { |
58 | 58 | // Done processing the queue since it's sorted by endTime. |
59 | 59 | break; |
|
71 | 71 | this._currentTime = finalTime; |
72 | 72 | } |
73 | 73 | } |
74 | | - |
| 74 | + |
75 | 75 | class FakeAsyncTestZoneSpec implements ZoneSpec { |
76 | 76 | static assertInZone(): void { |
77 | 77 | if (Zone.current.get('FakeAsyncTestZoneSpec') == null) { |
78 | 78 | throw new Error('The code should be running in the fakeAsync zone to call this function'); |
79 | 79 | } |
80 | 80 | } |
81 | | - |
| 81 | + |
82 | 82 | private _scheduler: Scheduler = new Scheduler(); |
83 | 83 | private _microtasks: Function[] = []; |
84 | 84 | private _lastError: Error = null; |
85 | 85 | private _uncaughtPromiseErrors: {rejection: any}[] = Promise[Zone['__symbol__']('uncaughtPromiseErrors')]; |
86 | | - |
| 86 | + |
87 | 87 | pendingPeriodicTimers: number[] = []; |
88 | 88 | pendingTimers: number[] = []; |
89 | 89 |
|
|
95 | 95 | completers: {onSuccess?: Function, onError?: Function}): Function { |
96 | 96 | return (...args): boolean => { |
97 | 97 | fn.apply(global, args); |
98 | | - |
| 98 | + |
99 | 99 | if (this._lastError === null) { // Success |
100 | 100 | if (completers.onSuccess != null) { |
101 | 101 | completers.onSuccess.apply(global); |
102 | 102 | } |
103 | 103 | // Flush microtasks only on success. |
104 | 104 | this.flushMicrotasks(); |
105 | 105 | } else { // Failure |
106 | | - if (completers.onError != null) { |
| 106 | + if (completers.onError != null) { |
107 | 107 | completers.onError.apply(global); |
108 | 108 | } |
109 | 109 | } |
110 | | - // Return true if there were no errors, false otherwise. |
| 110 | + // Return true if there were no errors, false otherwise. |
111 | 111 | return this._lastError === null; |
112 | | - } |
| 112 | + }; |
113 | 113 | } |
114 | | - |
| 114 | + |
115 | 115 | private static _removeTimer(timers: number[], id:number): void { |
116 | 116 | let index = timers.indexOf(id); |
117 | 117 | if (index > -1) { |
118 | 118 | timers.splice(index, 1); |
119 | 119 | } |
120 | 120 | } |
121 | | - |
| 121 | + |
122 | 122 | private _dequeueTimer(id: number): Function { |
123 | 123 | return () => { |
124 | 124 | FakeAsyncTestZoneSpec._removeTimer(this.pendingTimers, id); |
125 | 125 | }; |
126 | 126 | } |
127 | | - |
| 127 | + |
128 | 128 | private _requeuePeriodicTimer( |
129 | 129 | fn: Function, interval: number, args: any[], id: number): Function { |
130 | 130 | return () => { |
131 | 131 | // Requeue the timer callback if it's not been canceled. |
132 | 132 | if (this.pendingPeriodicTimers.indexOf(id) !== -1) { |
133 | 133 | this._scheduler.scheduleFunction(fn, interval, args, id); |
134 | 134 | } |
135 | | - } |
| 135 | + }; |
136 | 136 | } |
137 | | - |
| 137 | + |
138 | 138 | private _dequeuePeriodicTimer(id: number): Function { |
139 | 139 | return () => { |
140 | 140 | FakeAsyncTestZoneSpec._removeTimer(this.pendingPeriodicTimers, id); |
141 | 141 | }; |
142 | 142 | } |
143 | | - |
| 143 | + |
144 | 144 | private _setTimeout(fn: Function, delay: number, args: any[]): number { |
145 | 145 | let removeTimerFn = this._dequeueTimer(this._scheduler.nextId); |
146 | 146 | // Queue the callback and dequeue the timer on success and error. |
|
149 | 149 | this.pendingTimers.push(id); |
150 | 150 | return id; |
151 | 151 | } |
152 | | - |
| 152 | + |
153 | 153 | private _clearTimeout(id: number): void { |
154 | 154 | FakeAsyncTestZoneSpec._removeTimer(this.pendingTimers, id); |
155 | 155 | this._scheduler.removeScheduledFunctionWithId(id); |
156 | 156 | } |
157 | | - |
| 157 | + |
158 | 158 | private _setInterval(fn: Function, interval: number, ...args): number { |
159 | 159 | let id = this._scheduler.nextId; |
160 | 160 | let completers = {onSuccess: null, onError: this._dequeuePeriodicTimer(id)}; |
161 | 161 | let cb = this._fnAndFlush(fn, completers); |
162 | | - |
163 | | - // Use the callback created above to requeue on success. |
| 162 | + |
| 163 | + // Use the callback created above to requeue on success. |
164 | 164 | completers.onSuccess = this._requeuePeriodicTimer(cb, interval, args, id); |
165 | | - |
| 165 | + |
166 | 166 | // Queue the callback and dequeue the periodic timer only on error. |
167 | 167 | this._scheduler.scheduleFunction(cb, interval, args); |
168 | 168 | this.pendingPeriodicTimers.push(id); |
|
180 | 180 | this._lastError = null; |
181 | 181 | throw error; |
182 | 182 | } |
183 | | - |
| 183 | + |
184 | 184 | tick(millis: number = 0): void { |
185 | 185 | FakeAsyncTestZoneSpec.assertInZone(); |
186 | 186 | this.flushMicrotasks(); |
|
197 | 197 | // If there is an error stop processing the microtask queue and rethrow the error. |
198 | 198 | this._resetLastErrorAndThrow(); |
199 | 199 | } |
200 | | - } |
| 200 | + }; |
201 | 201 | while (this._microtasks.length > 0) { |
202 | 202 | let microtask = this._microtasks.shift(); |
203 | 203 | microtask(); |
|
249 | 249 | return delegate.cancelTask(target, task); |
250 | 250 | } |
251 | 251 | } |
252 | | - |
| 252 | + |
253 | 253 | onHandleError(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, |
254 | 254 | error: any): boolean { |
255 | | - this._lastError = error; |
| 255 | + this._lastError = error; |
256 | 256 | return false; // Don't propagate error to parent zone. |
257 | 257 | } |
258 | 258 | } |
|
0 commit comments