async-queue.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. /*
  2. YUI 3.4.1 (build 4118)
  3. Copyright 2011 Yahoo! Inc. All rights reserved.
  4. Licensed under the BSD License.
  5. http://yuilibrary.com/license/
  6. */
  7. YUI.add('async-queue', function(Y) {
  8. /**
  9. * <p>AsyncQueue allows you create a chain of function callbacks executed
  10. * via setTimeout (or synchronously) that are guaranteed to run in order.
  11. * Items in the queue can be promoted or removed. Start or resume the
  12. * execution chain with run(). pause() to temporarily delay execution, or
  13. * stop() to halt and clear the queue.</p>
  14. *
  15. * @module async-queue
  16. */
  17. /**
  18. * <p>A specialized queue class that supports scheduling callbacks to execute
  19. * sequentially, iteratively, even asynchronously.</p>
  20. *
  21. * <p>Callbacks can be function refs or objects with the following keys. Only
  22. * the <code>fn</code> key is required.</p>
  23. *
  24. * <ul>
  25. * <li><code>fn</code> -- The callback function</li>
  26. * <li><code>context</code> -- The execution context for the callbackFn.</li>
  27. * <li><code>args</code> -- Arguments to pass to callbackFn.</li>
  28. * <li><code>timeout</code> -- Millisecond delay before executing callbackFn.
  29. * (Applies to each iterative execution of callback)</li>
  30. * <li><code>iterations</code> -- Number of times to repeat the callback.
  31. * <li><code>until</code> -- Repeat the callback until this function returns
  32. * true. This setting trumps iterations.</li>
  33. * <li><code>autoContinue</code> -- Set to false to prevent the AsyncQueue from
  34. * executing the next callback in the Queue after
  35. * the callback completes.</li>
  36. * <li><code>id</code> -- Name that can be used to get, promote, get the
  37. * indexOf, or delete this callback.</li>
  38. * </ul>
  39. *
  40. * @class AsyncQueue
  41. * @extends EventTarget
  42. * @constructor
  43. * @param callback* {Function|Object} 0..n callbacks to seed the queue
  44. */
  45. Y.AsyncQueue = function() {
  46. this._init();
  47. this.add.apply(this, arguments);
  48. };
  49. var Queue = Y.AsyncQueue,
  50. EXECUTE = 'execute',
  51. SHIFT = 'shift',
  52. PROMOTE = 'promote',
  53. REMOVE = 'remove',
  54. isObject = Y.Lang.isObject,
  55. isFunction = Y.Lang.isFunction;
  56. /**
  57. * <p>Static default values used to populate callback configuration properties.
  58. * Preconfigured defaults include:</p>
  59. *
  60. * <ul>
  61. * <li><code>autoContinue</code>: <code>true</code></li>
  62. * <li><code>iterations</code>: 1</li>
  63. * <li><code>timeout</code>: 10 (10ms between callbacks)</li>
  64. * <li><code>until</code>: (function to run until iterations &lt;= 0)</li>
  65. * </ul>
  66. *
  67. * @property defaults
  68. * @type {Object}
  69. * @static
  70. */
  71. Queue.defaults = Y.mix({
  72. autoContinue : true,
  73. iterations : 1,
  74. timeout : 10,
  75. until : function () {
  76. this.iterations |= 0;
  77. return this.iterations <= 0;
  78. }
  79. }, Y.config.queueDefaults || {});
  80. Y.extend(Queue, Y.EventTarget, {
  81. /**
  82. * Used to indicate the queue is currently executing a callback.
  83. *
  84. * @property _running
  85. * @type {Boolean|Object} true for synchronous callback execution, the
  86. * return handle from Y.later for async callbacks.
  87. * Otherwise false.
  88. * @protected
  89. */
  90. _running : false,
  91. /**
  92. * Initializes the AsyncQueue instance properties and events.
  93. *
  94. * @method _init
  95. * @protected
  96. */
  97. _init : function () {
  98. Y.EventTarget.call(this, { prefix: 'queue', emitFacade: true });
  99. this._q = [];
  100. /**
  101. * Callback defaults for this instance. Static defaults that are not
  102. * overridden are also included.
  103. *
  104. * @property defaults
  105. * @type {Object}
  106. */
  107. this.defaults = {};
  108. this._initEvents();
  109. },
  110. /**
  111. * Initializes the instance events.
  112. *
  113. * @method _initEvents
  114. * @protected
  115. */
  116. _initEvents : function () {
  117. this.publish({
  118. 'execute' : { defaultFn : this._defExecFn, emitFacade: true },
  119. 'shift' : { defaultFn : this._defShiftFn, emitFacade: true },
  120. 'add' : { defaultFn : this._defAddFn, emitFacade: true },
  121. 'promote' : { defaultFn : this._defPromoteFn, emitFacade: true },
  122. 'remove' : { defaultFn : this._defRemoveFn, emitFacade: true }
  123. });
  124. },
  125. /**
  126. * Returns the next callback needing execution. If a callback is
  127. * configured to repeat via iterations or until, it will be returned until
  128. * the completion criteria is met.
  129. *
  130. * When the queue is empty, null is returned.
  131. *
  132. * @method next
  133. * @return {Function} the callback to execute
  134. */
  135. next : function () {
  136. var callback;
  137. while (this._q.length) {
  138. callback = this._q[0] = this._prepare(this._q[0]);
  139. if (callback && callback.until()) {
  140. this.fire(SHIFT, { callback: callback });
  141. callback = null;
  142. } else {
  143. break;
  144. }
  145. }
  146. return callback || null;
  147. },
  148. /**
  149. * Default functionality for the &quot;shift&quot; event. Shifts the
  150. * callback stored in the event object's <em>callback</em> property from
  151. * the queue if it is the first item.
  152. *
  153. * @method _defShiftFn
  154. * @param e {Event} The event object
  155. * @protected
  156. */
  157. _defShiftFn : function (e) {
  158. if (this.indexOf(e.callback) === 0) {
  159. this._q.shift();
  160. }
  161. },
  162. /**
  163. * Creates a wrapper function to execute the callback using the aggregated
  164. * configuration generated by combining the static AsyncQueue.defaults, the
  165. * instance defaults, and the specified callback settings.
  166. *
  167. * The wrapper function is decorated with the callback configuration as
  168. * properties for runtime modification.
  169. *
  170. * @method _prepare
  171. * @param callback {Object|Function} the raw callback
  172. * @return {Function} a decorated function wrapper to execute the callback
  173. * @protected
  174. */
  175. _prepare: function (callback) {
  176. if (isFunction(callback) && callback._prepared) {
  177. return callback;
  178. }
  179. var config = Y.merge(
  180. Queue.defaults,
  181. { context : this, args: [], _prepared: true },
  182. this.defaults,
  183. (isFunction(callback) ? { fn: callback } : callback)),
  184. wrapper = Y.bind(function () {
  185. if (!wrapper._running) {
  186. wrapper.iterations--;
  187. }
  188. if (isFunction(wrapper.fn)) {
  189. wrapper.fn.apply(wrapper.context || Y,
  190. Y.Array(wrapper.args));
  191. }
  192. }, this);
  193. return Y.mix(wrapper, config);
  194. },
  195. /**
  196. * Sets the queue in motion. All queued callbacks will be executed in
  197. * order unless pause() or stop() is called or if one of the callbacks is
  198. * configured with autoContinue: false.
  199. *
  200. * @method run
  201. * @return {AsyncQueue} the AsyncQueue instance
  202. * @chainable
  203. */
  204. run : function () {
  205. var callback,
  206. cont = true;
  207. for (callback = this.next();
  208. cont && callback && !this.isRunning();
  209. callback = this.next())
  210. {
  211. cont = (callback.timeout < 0) ?
  212. this._execute(callback) :
  213. this._schedule(callback);
  214. }
  215. if (!callback) {
  216. /**
  217. * Event fired after the last queued callback is executed.
  218. * @event complete
  219. */
  220. this.fire('complete');
  221. }
  222. return this;
  223. },
  224. /**
  225. * Handles the execution of callbacks. Returns a boolean indicating
  226. * whether it is appropriate to continue running.
  227. *
  228. * @method _execute
  229. * @param callback {Object} the callback object to execute
  230. * @return {Boolean} whether the run loop should continue
  231. * @protected
  232. */
  233. _execute : function (callback) {
  234. this._running = callback._running = true;
  235. callback.iterations--;
  236. this.fire(EXECUTE, { callback: callback });
  237. var cont = this._running && callback.autoContinue;
  238. this._running = callback._running = false;
  239. return cont;
  240. },
  241. /**
  242. * Schedules the execution of asynchronous callbacks.
  243. *
  244. * @method _schedule
  245. * @param callback {Object} the callback object to execute
  246. * @return {Boolean} whether the run loop should continue
  247. * @protected
  248. */
  249. _schedule : function (callback) {
  250. this._running = Y.later(callback.timeout, this, function () {
  251. if (this._execute(callback)) {
  252. this.run();
  253. }
  254. });
  255. return false;
  256. },
  257. /**
  258. * Determines if the queue is waiting for a callback to complete execution.
  259. *
  260. * @method isRunning
  261. * @return {Boolean} true if queue is waiting for a
  262. * from any initiated transactions
  263. */
  264. isRunning : function () {
  265. return !!this._running;
  266. },
  267. /**
  268. * Default functionality for the &quot;execute&quot; event. Executes the
  269. * callback function
  270. *
  271. * @method _defExecFn
  272. * @param e {Event} the event object
  273. * @protected
  274. */
  275. _defExecFn : function (e) {
  276. e.callback();
  277. },
  278. /**
  279. * Add any number of callbacks to the end of the queue. Callbacks may be
  280. * provided as functions or objects.
  281. *
  282. * @method add
  283. * @param callback* {Function|Object} 0..n callbacks
  284. * @return {AsyncQueue} the AsyncQueue instance
  285. * @chainable
  286. */
  287. add : function () {
  288. this.fire('add', { callbacks: Y.Array(arguments,0,true) });
  289. return this;
  290. },
  291. /**
  292. * Default functionality for the &quot;add&quot; event. Adds the callbacks
  293. * in the event facade to the queue. Callbacks successfully added to the
  294. * queue are present in the event's <code>added</code> property in the
  295. * after phase.
  296. *
  297. * @method _defAddFn
  298. * @param e {Event} the event object
  299. * @protected
  300. */
  301. _defAddFn : function(e) {
  302. var _q = this._q,
  303. added = [];
  304. Y.Array.each(e.callbacks, function (c) {
  305. if (isObject(c)) {
  306. _q.push(c);
  307. added.push(c);
  308. }
  309. });
  310. e.added = added;
  311. },
  312. /**
  313. * Pause the execution of the queue after the execution of the current
  314. * callback completes. If called from code outside of a queued callback,
  315. * clears the timeout for the pending callback. Paused queue can be
  316. * restarted with q.run()
  317. *
  318. * @method pause
  319. * @return {AsyncQueue} the AsyncQueue instance
  320. * @chainable
  321. */
  322. pause: function () {
  323. if (isObject(this._running)) {
  324. this._running.cancel();
  325. }
  326. this._running = false;
  327. return this;
  328. },
  329. /**
  330. * Stop and clear the queue after the current execution of the
  331. * current callback completes.
  332. *
  333. * @method stop
  334. * @return {AsyncQueue} the AsyncQueue instance
  335. * @chainable
  336. */
  337. stop : function () {
  338. this._q = [];
  339. return this.pause();
  340. },
  341. /**
  342. * Returns the current index of a callback. Pass in either the id or
  343. * callback function from getCallback.
  344. *
  345. * @method indexOf
  346. * @param callback {String|Function} the callback or its specified id
  347. * @return {Number} index of the callback or -1 if not found
  348. */
  349. indexOf : function (callback) {
  350. var i = 0, len = this._q.length, c;
  351. for (; i < len; ++i) {
  352. c = this._q[i];
  353. if (c === callback || c.id === callback) {
  354. return i;
  355. }
  356. }
  357. return -1;
  358. },
  359. /**
  360. * Retrieve a callback by its id. Useful to modify the configuration
  361. * while the queue is running.
  362. *
  363. * @method getCallback
  364. * @param id {String} the id assigned to the callback
  365. * @return {Object} the callback object
  366. */
  367. getCallback : function (id) {
  368. var i = this.indexOf(id);
  369. return (i > -1) ? this._q[i] : null;
  370. },
  371. /**
  372. * Promotes the named callback to the top of the queue. If a callback is
  373. * currently executing or looping (via until or iterations), the promotion
  374. * is scheduled to occur after the current callback has completed.
  375. *
  376. * @method promote
  377. * @param callback {String|Object} the callback object or a callback's id
  378. * @return {AsyncQueue} the AsyncQueue instance
  379. * @chainable
  380. */
  381. promote : function (callback) {
  382. var payload = { callback : callback },e;
  383. if (this.isRunning()) {
  384. e = this.after(SHIFT, function () {
  385. this.fire(PROMOTE, payload);
  386. e.detach();
  387. }, this);
  388. } else {
  389. this.fire(PROMOTE, payload);
  390. }
  391. return this;
  392. },
  393. /**
  394. * <p>Default functionality for the &quot;promote&quot; event. Promotes the
  395. * named callback to the head of the queue.</p>
  396. *
  397. * <p>The event object will contain a property &quot;callback&quot;, which
  398. * holds the id of a callback or the callback object itself.</p>
  399. *
  400. * @method _defPromoteFn
  401. * @param e {Event} the custom event
  402. * @protected
  403. */
  404. _defPromoteFn : function (e) {
  405. var i = this.indexOf(e.callback),
  406. promoted = (i > -1) ? this._q.splice(i,1)[0] : null;
  407. e.promoted = promoted;
  408. if (promoted) {
  409. this._q.unshift(promoted);
  410. }
  411. },
  412. /**
  413. * Removes the callback from the queue. If the queue is active, the
  414. * removal is scheduled to occur after the current callback has completed.
  415. *
  416. * @method remove
  417. * @param callback {String|Object} the callback object or a callback's id
  418. * @return {AsyncQueue} the AsyncQueue instance
  419. * @chainable
  420. */
  421. remove : function (callback) {
  422. var payload = { callback : callback },e;
  423. // Can't return the removed callback because of the deferral until
  424. // current callback is complete
  425. if (this.isRunning()) {
  426. e = this.after(SHIFT, function () {
  427. this.fire(REMOVE, payload);
  428. e.detach();
  429. },this);
  430. } else {
  431. this.fire(REMOVE, payload);
  432. }
  433. return this;
  434. },
  435. /**
  436. * <p>Default functionality for the &quot;remove&quot; event. Removes the
  437. * callback from the queue.</p>
  438. *
  439. * <p>The event object will contain a property &quot;callback&quot;, which
  440. * holds the id of a callback or the callback object itself.</p>
  441. *
  442. * @method _defRemoveFn
  443. * @param e {Event} the custom event
  444. * @protected
  445. */
  446. _defRemoveFn : function (e) {
  447. var i = this.indexOf(e.callback);
  448. e.removed = (i > -1) ? this._q.splice(i,1)[0] : null;
  449. },
  450. /**
  451. * Returns the number of callbacks in the queue.
  452. *
  453. * @method size
  454. * @return {Number}
  455. */
  456. size : function () {
  457. // next() flushes callbacks that have met their until() criteria and
  458. // therefore shouldn't count since they wouldn't execute anyway.
  459. if (!this.isRunning()) {
  460. this.next();
  461. }
  462. return this._q.length;
  463. }
  464. });
  465. }, '3.4.1' ,{requires:['event-custom']});