API Docs for: 3.13.0
Show:

File: async-queue/js/async-queue.js

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