|
1 YUI.add('timers', function (Y, NAME) { |
|
2 |
|
3 /** |
|
4 Provides utilities for timed asynchronous callback execution. |
|
5 Y.soon is a setImmediate/process.nextTick/setTimeout wrapper. |
|
6 |
|
7 This module includes [asap.js](https://github.com/kriskowal/asap) for scheduling |
|
8 asynchronous tasks. |
|
9 |
|
10 @module timers |
|
11 @author Steven Olmsted |
|
12 **/ |
|
13 |
|
14 // Hack. asap.js is written as a Node module and expects require, module and |
|
15 // global to be available in the module's scope. |
|
16 var module = {}, |
|
17 global = Y.config.global; |
|
18 |
|
19 // `asap` only requires a `queue` module that is bundled into this same file. |
|
20 function require(mod) { |
|
21 return Queue; |
|
22 } |
|
23 "use strict"; |
|
24 |
|
25 module.exports = Queue; |
|
26 function Queue(capacity) { |
|
27 this.capacity = this.snap(capacity); |
|
28 this.length = 0; |
|
29 this.front = 0; |
|
30 this.initialize(); |
|
31 } |
|
32 |
|
33 Queue.prototype.push = function (value) { |
|
34 var length = this.length; |
|
35 if (this.capacity <= length) { |
|
36 this.grow(this.snap(this.capacity * this.growFactor)); |
|
37 } |
|
38 var index = (this.front + length) & (this.capacity - 1); |
|
39 this[index] = value; |
|
40 this.length = length + 1; |
|
41 }; |
|
42 |
|
43 Queue.prototype.shift = function () { |
|
44 var front = this.front; |
|
45 var result = this[front]; |
|
46 |
|
47 this[front] = void 0; |
|
48 this.front = (front + 1) & (this.capacity - 1); |
|
49 this.length--; |
|
50 return result; |
|
51 }; |
|
52 |
|
53 Queue.prototype.grow = function (capacity) { |
|
54 var oldFront = this.front; |
|
55 var oldCapacity = this.capacity; |
|
56 var oldQueue = new Array(oldCapacity); |
|
57 var length = this.length; |
|
58 |
|
59 copy(this, 0, oldQueue, 0, oldCapacity); |
|
60 this.capacity = capacity; |
|
61 this.initialize(); |
|
62 this.front = 0; |
|
63 if (oldFront + length <= oldCapacity) { |
|
64 // Can perform direct linear copy |
|
65 copy(oldQueue, oldFront, this, 0, length); |
|
66 } else { |
|
67 // Cannot perform copy directly, perform as much as possible at the |
|
68 // end, and then copy the rest to the beginning of the buffer |
|
69 var lengthBeforeWrapping = |
|
70 length - ((oldFront + length) & (oldCapacity - 1)); |
|
71 copy( |
|
72 oldQueue, |
|
73 oldFront, |
|
74 this, |
|
75 0, |
|
76 lengthBeforeWrapping |
|
77 ); |
|
78 copy( |
|
79 oldQueue, |
|
80 0, |
|
81 this, |
|
82 lengthBeforeWrapping, |
|
83 length - lengthBeforeWrapping |
|
84 ); |
|
85 } |
|
86 }; |
|
87 |
|
88 Queue.prototype.initialize = function () { |
|
89 var length = this.capacity; |
|
90 for (var i = 0; i < length; ++i) { |
|
91 this[i] = void 0; |
|
92 } |
|
93 }; |
|
94 |
|
95 Queue.prototype.snap = function (capacity) { |
|
96 if (typeof capacity !== "number") { |
|
97 return this.minCapacity; |
|
98 } |
|
99 return pow2AtLeast( |
|
100 Math.min(this.maxCapacity, Math.max(this.minCapacity, capacity)) |
|
101 ); |
|
102 }; |
|
103 |
|
104 Queue.prototype.maxCapacity = (1 << 30) | 0; |
|
105 Queue.prototype.minCapacity = 16; |
|
106 Queue.prototype.growFactor = 8; |
|
107 |
|
108 function copy(source, sourceIndex, target, targetIndex, length) { |
|
109 for (var index = 0; index < length; ++index) { |
|
110 target[index + targetIndex] = source[index + sourceIndex]; |
|
111 } |
|
112 } |
|
113 |
|
114 function pow2AtLeast(n) { |
|
115 n = n >>> 0; |
|
116 n = n - 1; |
|
117 n = n | (n >> 1); |
|
118 n = n | (n >> 2); |
|
119 n = n | (n >> 4); |
|
120 n = n | (n >> 8); |
|
121 n = n | (n >> 16); |
|
122 return n + 1; |
|
123 } |
|
124 "use strict"; |
|
125 |
|
126 // Use the fastest possible means to execute a task in a future turn |
|
127 // of the event loop. |
|
128 |
|
129 // Queue is a circular buffer with good locality of reference and doesn't |
|
130 // allocate new memory unless there are more than `InitialCapacity` parallel |
|
131 // tasks in which case it will resize itself generously to x8 more capacity. |
|
132 // The use case of asap should require no or few amount of resizes during |
|
133 // runtime. |
|
134 // Calling a task frees a slot immediately so if the calling |
|
135 // has a side effect of queuing itself again, it can be sustained |
|
136 // without additional memory |
|
137 // Queue specifically uses |
|
138 // http://en.wikipedia.org/wiki/Circular_buffer#Use_a_Fill_Count |
|
139 // Because: |
|
140 // 1. We need fast .length operation, since queue |
|
141 // could have changed after every iteration |
|
142 // 2. Modulus can be negated by using power-of-two |
|
143 // capacities and replacing it with bitwise AND |
|
144 // 3. It will not be used in a multi-threaded situation. |
|
145 |
|
146 var Queue = require("./queue"); |
|
147 |
|
148 //1024 = InitialCapacity |
|
149 var queue = new Queue(1024); |
|
150 var flushing = false; |
|
151 var requestFlush = void 0; |
|
152 var hasSetImmediate = typeof setImmediate === "function"; |
|
153 var domain; |
|
154 |
|
155 // Avoid shims from browserify. |
|
156 // The existence of `global` in browsers is guaranteed by browserify. |
|
157 var process = global.process; |
|
158 |
|
159 // Note that some fake-Node environments, |
|
160 // like the Mocha test runner, introduce a `process` global. |
|
161 var isNodeJS = !!process && ({}).toString.call(process) === "[object process]"; |
|
162 |
|
163 function flush() { |
|
164 /* jshint loopfunc: true */ |
|
165 |
|
166 while (queue.length > 0) { |
|
167 var task = queue.shift(); |
|
168 |
|
169 try { |
|
170 task.call(); |
|
171 |
|
172 } catch (e) { |
|
173 if (isNodeJS) { |
|
174 // In node, uncaught exceptions are considered fatal errors. |
|
175 // Re-throw them to interrupt flushing! |
|
176 |
|
177 // Ensure continuation if an uncaught exception is suppressed |
|
178 // listening process.on("uncaughtException") or domain("error"). |
|
179 requestFlush(); |
|
180 |
|
181 throw e; |
|
182 |
|
183 } else { |
|
184 // In browsers, uncaught exceptions are not fatal. |
|
185 // Re-throw them asynchronously to avoid slow-downs. |
|
186 setTimeout(function () { |
|
187 throw e; |
|
188 }, 0); |
|
189 } |
|
190 } |
|
191 } |
|
192 |
|
193 flushing = false; |
|
194 } |
|
195 |
|
196 if (isNodeJS) { |
|
197 // Node.js |
|
198 requestFlush = function () { |
|
199 // Ensure flushing is not bound to any domain. |
|
200 var currentDomain = process.domain; |
|
201 if (currentDomain) { |
|
202 domain = domain || (1,require)("domain"); |
|
203 domain.active = process.domain = null; |
|
204 } |
|
205 |
|
206 // Avoid tick recursion - use setImmediate if it exists. |
|
207 if (flushing && hasSetImmediate) { |
|
208 setImmediate(flush); |
|
209 } else { |
|
210 process.nextTick(flush); |
|
211 } |
|
212 |
|
213 if (currentDomain) { |
|
214 domain.active = process.domain = currentDomain; |
|
215 } |
|
216 }; |
|
217 |
|
218 } else if (hasSetImmediate) { |
|
219 // In IE10, or https://github.com/NobleJS/setImmediate |
|
220 requestFlush = function () { |
|
221 setImmediate(flush); |
|
222 }; |
|
223 |
|
224 } else if (typeof MessageChannel !== "undefined") { |
|
225 // modern browsers |
|
226 // http://www.nonblocking.io/2011/06/windownexttick.html |
|
227 var channel = new MessageChannel(); |
|
228 // At least Safari Version 6.0.5 (8536.30.1) intermittently cannot create |
|
229 // working message ports the first time a page loads. |
|
230 channel.port1.onmessage = function () { |
|
231 requestFlush = requestPortFlush; |
|
232 channel.port1.onmessage = flush; |
|
233 flush(); |
|
234 }; |
|
235 var requestPortFlush = function () { |
|
236 // Opera requires us to provide a message payload, regardless of |
|
237 // whether we use it. |
|
238 channel.port2.postMessage(0); |
|
239 }; |
|
240 requestFlush = function () { |
|
241 setTimeout(flush, 0); |
|
242 requestPortFlush(); |
|
243 }; |
|
244 |
|
245 } else { |
|
246 // old browsers |
|
247 requestFlush = function () { |
|
248 setTimeout(flush, 0); |
|
249 }; |
|
250 } |
|
251 |
|
252 function asap(task) { |
|
253 if (isNodeJS && process.domain) { |
|
254 task = process.domain.bind(task); |
|
255 } |
|
256 |
|
257 queue.push(task); |
|
258 |
|
259 if (!flushing) { |
|
260 requestFlush(); |
|
261 flushing = true; |
|
262 } |
|
263 }; |
|
264 |
|
265 module.exports = asap; |
|
266 /** |
|
267 Y.soon accepts a callback function. The callback function will be called |
|
268 once in a future turn of the JavaScript event loop. If the function |
|
269 requires a specific execution context or arguments, wrap it with Y.bind. |
|
270 Y.soon returns an object with a cancel method. If the cancel method is |
|
271 called before the callback function, the callback function won't be |
|
272 called. |
|
273 |
|
274 @method soon |
|
275 @for YUI |
|
276 @param {Function} callbackFunction |
|
277 @return {Object} An object with a cancel method. If the cancel method is |
|
278 called before the callback function, the callback function won't be |
|
279 called. |
|
280 **/ |
|
281 function soon(callbackFunction) { |
|
282 var canceled; |
|
283 |
|
284 soon._asynchronizer(function () { |
|
285 // Some asynchronizers may provide their own cancellation |
|
286 // methods such as clearImmediate or clearTimeout but some |
|
287 // asynchronizers do not. For simplicity, cancellation is |
|
288 // entirely handled here rather than wrapping the other methods. |
|
289 // All asynchronizers are expected to always call this anonymous |
|
290 // function. |
|
291 if (!canceled) { |
|
292 callbackFunction(); |
|
293 } |
|
294 }); |
|
295 |
|
296 return { |
|
297 cancel: function () { |
|
298 canceled = 1; |
|
299 } |
|
300 }; |
|
301 } |
|
302 |
|
303 soon._asynchronizer = asap; |
|
304 soon._impl = 'asap'; |
|
305 |
|
306 Y.soon = soon; |
|
307 |
|
308 |
|
309 }, '@VERSION@', {"requires": ["yui-base"]}); |