power-queue.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727
  1. // Rig weak dependencies
  2. if (typeof MicroQueue === 'undefined' && Package['micro-queue']) {
  3. MicroQueue = Package['micro-queue'].MicroQueue;
  4. }
  5. if (typeof ReactiveList === 'undefined' && Package['reactive-list']) {
  6. ReactiveList = Package['reactive-list'].ReactiveList;
  7. }
  8. // Rig weak dependencies in +0.9.1
  9. if (typeof MicroQueue === 'undefined' && Package['wekan-cfs-micro-queue']) {
  10. MicroQueue = Package['wekan-cfs-micro-queue'].MicroQueue;
  11. }
  12. if (typeof ReactiveList === 'undefined' && Package['wekan-cfs-reactive-list']) {
  13. ReactiveList = Package['wekan-cfs-reactive-list'].ReactiveList;
  14. }
  15. /**
  16. * Creates an instance of a power queue // Testing inline comment
  17. * [Check out demo](http://power-queue-test.meteor.com/)
  18. *
  19. * @constructor
  20. * @self powerqueue
  21. * @param {object} [options] Settings
  22. * @param {boolean} [options.filo=false] Make it a first in last out queue
  23. * @param {boolean} [options.isPaused=false] Set queue paused
  24. * @param {boolean} [options.autostart=true] May adding a task start the queue
  25. * @param {string} [options.name="Queue"] Name of the queue
  26. * @param {number} [options.maxProcessing=1] Limit of simultanous running tasks
  27. * @param {number} [options.maxFailures = 5] Limit retries of failed tasks, if 0 or below we allow infinite failures
  28. * @param {number} [options.jumpOnFailure = true] Jump to next task and retry failed task later
  29. * @param {boolean} [options.debug=false] Log verbose messages to the console
  30. * @param {boolean} [options.reactive=true] Set whether or not this queue should be reactive
  31. * @param {boolean} [options.onAutostart] Callback for the queue autostart event
  32. * @param {boolean} [options.onPaused] Callback for the queue paused event
  33. * @param {boolean} [options.onReleased] Callback for the queue release event
  34. * @param {boolean} [options.onEnded] Callback for the queue end event
  35. * @param {[SpinalQueue](spinal-queue.spec.md)} [options.spinalQueue] Set spinal queue uses pr. default `MicroQueue` or `ReactiveList` if added to the project
  36. */
  37. PowerQueue = function(options) {
  38. var self = this;
  39. var test = 5;
  40. self.reactive = (options && options.reactive === false) ? false : true;
  41. // Allow user to use another micro-queue #3
  42. // We try setting the ActiveQueue to MicroQueue if installed in the app
  43. var ActiveQueue = (typeof MicroQueue !== 'undefined') && MicroQueue || undefined;
  44. // If ReactiveList is added to the project we use this over MicroQueue
  45. ActiveQueue = (typeof ReactiveList !== 'undefined') && ReactiveList || ActiveQueue;
  46. // We allow user to overrule and set a custom spinal-queue spec complient queue
  47. if (options && typeof options.spinalQueue !== 'undefined') {
  48. ActiveQueue = options.spinalQueue;
  49. }
  50. if (typeof ActiveQueue === 'undefined') {
  51. console.log('Error: You need to add a spinal queue to the project');
  52. console.log('Please add "micro-queue", "reactive-list" to the project');
  53. throw new Error('Please add "micro-queue", "reactive-list" or other spinalQueue compatible packages');
  54. }
  55. // Default is fifo lilo
  56. self.invocations = new ActiveQueue({
  57. //
  58. sort: (options && (options.filo || options.lifo)),
  59. reactive: self.reactive
  60. });
  61. //var self.invocations = new ReactiveList(queueOrder);
  62. // List of current tasks being processed
  63. self._processList = new ActiveQueue({
  64. reactive: self.reactive
  65. }); //ReactiveList();
  66. // Max number of simultanious tasks being processed
  67. self._maxProcessing = new ReactiveProperty(options && options.maxProcessing || 1, self.reactive);
  68. // Reactive number of tasks being processed
  69. self._isProcessing = new ReactiveProperty(0, self.reactive);
  70. // Boolean indicating if queue is paused or not
  71. self._paused = new ReactiveProperty((options && options.isPaused || false), self.reactive);
  72. // Boolean indicator for queue status active / running (can still be paused)
  73. self._running = new ReactiveProperty(false, self.reactive);
  74. // Counter for errors, errors are triggered if maxFailures is exeeded
  75. self._errors = new ReactiveProperty(0, self.reactive);
  76. // Counter for task failures, contains error count
  77. self._failures = new ReactiveProperty(0, self.reactive);
  78. // On failure jump to new task - if false the current task is rerun until error
  79. self._jumpOnFailure = (options && options.jumpOnFailure === false) ? false : true;
  80. // Count of all added tasks
  81. self._maxLength = new ReactiveProperty(0, self.reactive);
  82. // Boolean indicate whether or not a "add" task is allowed to start the queue
  83. self._autostart = new ReactiveProperty( ((options && options.autostart === false) ? false : true), self.reactive);
  84. // Limit times a task is allowed to fail and be rerun later before triggering an error
  85. self._maxFailures = new ReactiveProperty( (options && options.maxFailures || 5), self.reactive);
  86. // Name / title of this queue - Not used - should deprecate
  87. self.title = options && options.name || 'Queue';
  88. // debug - will print error / failures passed to next
  89. self.debug = !!(options && options.debug);
  90. /** @method PowerQueue.total
  91. * @reactive
  92. * @returns {number} The total number of tasks added to this queue
  93. */
  94. self.total = self._maxLength.get;
  95. /** @method PowerQueue.isPaused
  96. * @reactive
  97. * @returns {boolean} Status of the paused state of the queue
  98. */
  99. self.isPaused = self._paused.get;
  100. /** @method PowerQueue.processing
  101. * @reactive
  102. * @returns {number} Number of tasks currently being processed
  103. */
  104. self.processing = self._isProcessing.get;
  105. /** @method PowerQueue.errors
  106. * @reactive
  107. * @returns {number} The total number of errors
  108. * Errors are triggered when [maxFailures](PowerQueue.maxFailures) are exeeded
  109. */
  110. self.errors = self._errors.get;
  111. /** @method PowerQueue.failures
  112. * @reactive
  113. * @returns {number} The total number of failed tasks
  114. */
  115. self.failures = self._failures.get;
  116. /** @method PowerQueue.isRunning
  117. * @reactive
  118. * @returns {boolean} True if the queue is running
  119. * > NOTE: The task can be paused but marked as running
  120. */
  121. self.isRunning = self._running.get;
  122. /** @method PowerQueue.maxProcessing Get setter for maxProcessing
  123. * @param {number} [max] If not used this function works as a getter
  124. * @reactive
  125. * @returns {number} Maximum number of simultaneous processing tasks
  126. *
  127. * Example:
  128. * ```js
  129. * foo.maxProcessing(); // Works as a getter and returns the current value
  130. * foo.maxProcessing(20); // This sets the value to 20
  131. * ```
  132. */
  133. self.maxProcessing = self._maxProcessing.getset;
  134. self._maxProcessing.onChange = function() {
  135. // The user can change the max allowed processing tasks up or down here...
  136. // Update the throttle up
  137. self.updateThrottleUp();
  138. // Update the throttle down
  139. self.updateThrottleDown();
  140. };
  141. /** @method PowerQueue.autostart Get setter for autostart
  142. * @param {boolean} [autorun] If not used this function works as a getter
  143. * @reactive
  144. * @returns {boolean} If adding a task may trigger the queue to start
  145. *
  146. * Example:
  147. * ```js
  148. * foo.autostart(); // Works as a getter and returns the current value
  149. * foo.autostart(true); // This sets the value to true
  150. * ```
  151. */
  152. self.autostart = self._autostart.getset;
  153. /** @method PowerQueue.maxFailures Get setter for maxFailures
  154. * @param {number} [max] If not used this function works as a getter
  155. * @reactive
  156. * @returns {number} The maximum for failures pr. task before triggering an error
  157. *
  158. * Example:
  159. * ```js
  160. * foo.maxFailures(); // Works as a getter and returns the current value
  161. * foo.maxFailures(10); // This sets the value to 10
  162. * ```
  163. */
  164. self.maxFailures = self._maxFailures.getset;
  165. /** @callback PowerQueue.onPaused
  166. * Is called when queue is ended
  167. */
  168. self.onPaused = options && options.onPaused || function() {
  169. self.debug && console.log(self.title + ' ENDED');
  170. };
  171. /** @callback PowerQueue.onEnded
  172. * Is called when queue is ended
  173. */
  174. self.onEnded = options && options.onEnded || function() {
  175. self.debug && console.log(self.title + ' ENDED');
  176. };
  177. /** @callback PowerQueue.onRelease
  178. * Is called when queue is released
  179. */
  180. self.onRelease = options && options.onRelease || function() {
  181. self.debug && console.log(self.title + ' RELEASED');
  182. };
  183. /** @callback PowerQueue.onAutostart
  184. * Is called when queue is auto started
  185. */
  186. self.onAutostart = options && options.onAutostart || function() {
  187. self.debug && console.log(self.title + ' Autostart');
  188. };
  189. };
  190. /** @method PowerQueue.prototype.processList
  191. * @reactive
  192. * @returns {array} List of tasks currently being processed
  193. */
  194. PowerQueue.prototype.processingList = function() {
  195. var self = this;
  196. return self._processList.fetch();
  197. };
  198. /** @method PowerQueue.prototype.isHalted
  199. * @reactive
  200. * @returns {boolean} True if the queue is not running or paused
  201. */
  202. PowerQueue.prototype.isHalted = function() {
  203. var self = this;
  204. return (!self._running.get() || self._paused.get());
  205. };
  206. /** @method PowerQueue.prototype.length
  207. * @reactive
  208. * @returns {number} Number of tasks left in queue to be processed
  209. */
  210. PowerQueue.prototype.length = function() {
  211. var self = this;
  212. return self.invocations.length();
  213. };
  214. /** @method PowerQueue.prototype.progress
  215. * @reactive
  216. * @returns {number} 0 .. 100 % Indicates the status of the queue
  217. */
  218. PowerQueue.prototype.progress = function() {
  219. var self = this;
  220. var progress = self._maxLength.get() - self.invocations.length() - self._isProcessing.get();
  221. if (self._maxLength.value > 0) {
  222. return Math.round(progress / self._maxLength.value * 100);
  223. }
  224. return 0;
  225. };
  226. /** @method PowerQueue.prototype.usage
  227. * @reactive
  228. * @returns {number} 0 .. 100 % Indicates resource usage of the queue
  229. */
  230. PowerQueue.prototype.usage = function() {
  231. var self = this;
  232. return Math.round(self._isProcessing.get() / self._maxProcessing.get() * 100);
  233. };
  234. /** @method PowerQueue.prototype.reset Reset the queue
  235. * Calling this will:
  236. * * stop the queue
  237. * * paused to false
  238. * * Discart all queue data
  239. *
  240. * > NOTE: At the moment if the queue has processing tasks they can change
  241. * > the `errors` and `failures` counters. This could change in the future or
  242. * > be prevented by creating a whole new instance of the `PowerQueue`
  243. */
  244. PowerQueue.prototype.reset = function() {
  245. var self = this;
  246. self.debug && console.log(self.title + ' RESET');
  247. self._running.set(false);
  248. self._paused.set(false);
  249. self.invocations.reset();
  250. self._processList.reset();
  251. // // Loop through the processing tasks and reset these
  252. // self._processList.forEach(function(data) {
  253. // if (data.queue instanceof PowerQueue) {
  254. // data.queue.reset();
  255. // }
  256. // }, true);
  257. self._maxLength.set(0);
  258. self._failures.set(0);
  259. self._errors.set(0);
  260. };
  261. /** @method PowerQueue._autoStartTasks
  262. * @private
  263. *
  264. * This method defines the autostart algorithm that allows add task to trigger
  265. * a start of the queue if queue is not paused.
  266. */
  267. PowerQueue.prototype._autoStartTasks = function() {
  268. var self = this;
  269. // We dont start anything by ourselfs if queue is paused
  270. if (!self._paused.value) {
  271. // Queue is not running and we are set to autostart so we start the queue
  272. if (!self._running.value && self._autostart.value) {
  273. // Trigger callback / event
  274. self.onAutostart();
  275. // Set queue as running
  276. self._running.set(true);
  277. }
  278. // Make sure that we use all available resources
  279. if (self._running.value) {
  280. // Call next to start up the queue
  281. self.next(null);
  282. }
  283. }
  284. };
  285. /** @method PowerQueue.prototype.add
  286. * @param {any} data The task to be handled
  287. * @param {number} [failures] Used internally to Pass on number of failures.
  288. */
  289. PowerQueue.prototype.add = function(data, failures, id) {
  290. var self = this;
  291. // Assign new id to task
  292. var assignNewId = self._jumpOnFailure || typeof id === 'undefined';
  293. // Set the task id
  294. var taskId = (assignNewId) ? self._maxLength.value + 1 : id;
  295. // self.invocations.add({ _id: currentId, data: data, failures: failures || 0 }, reversed);
  296. self.invocations.insert(taskId, { _id: taskId, data: data, failures: failures || 0 });
  297. // If we assigned new id then increase length
  298. if (assignNewId) self._maxLength.inc();
  299. self._autoStartTasks();
  300. };
  301. /** @method PowerQueue.prototype.updateThrottleUp
  302. * @private
  303. *
  304. * Calling this method will update the throttle on the queue adding tasks.
  305. *
  306. * > Note: Currently we only support the PowerQueue - but we could support
  307. * > a more general interface for pauseable tasks or other usecases.
  308. */
  309. PowerQueue.prototype.updateThrottleUp = function() {
  310. var self = this;
  311. // How many additional tasks can we handle?
  312. var availableSlots = self._maxProcessing.value - self._isProcessing.value;
  313. // If we can handle more, we have more, we're running, and we're not paused
  314. if (!self._paused.value && self._running.value && availableSlots > 0 && self.invocations._length > 0) {
  315. // Increase counter of current number of tasks being processed
  316. self._isProcessing.inc();
  317. // Run task
  318. self.runTask(self.invocations.getFirstItem());
  319. // Repeat recursively; this is better than a for loop to avoid blocking the UI
  320. self.updateThrottleUp();
  321. }
  322. };
  323. /** @method PowerQueue.prototype.updateThrottleDown
  324. * @private
  325. *
  326. * Calling this method will update the throttle on the queue pause tasks.
  327. *
  328. * > Note: Currently we only support the PowerQueue - but we could support
  329. * > a more general interface for pauseable tasks or other usecases.
  330. */
  331. PowerQueue.prototype.updateThrottleDown = function() {
  332. var self = this;
  333. // Calculate the differece between acutuall processing tasks and target
  334. var diff = self._isProcessing.value - self._maxProcessing.value;
  335. // If the diff is more than 0 then we have many tasks processing.
  336. if (diff > 0) {
  337. // We pause the latest added tasks
  338. self._processList.forEachReverse(function(data) {
  339. if (diff > 0 && data.queue instanceof PowerQueue) {
  340. diff--;
  341. // We dont mind calling pause on multiple times on each task
  342. // theres a simple check going on preventing any duplicate actions
  343. data.queue.pause();
  344. }
  345. }, true);
  346. }
  347. };
  348. /** @method PowerQueue.prototype.next
  349. * @param {string} [err] Error message if task failed
  350. * > * Can pass in `null` to start the queue
  351. * > * Passing in a string to `next` will trigger a failure
  352. * > * Passing nothing will simply let the next task run
  353. * `next` is handed into the [taskHandler](PowerQueue.taskHandler) as a
  354. * callback to mark an error or end of current task
  355. */
  356. PowerQueue.prototype.next = function(err) {
  357. var self = this;
  358. // Primary concern is to throttle up because we are either:
  359. // 1. Starting the queue
  360. // 2. Starting next task
  361. //
  362. // This function does not shut down running tasks
  363. self.updateThrottleUp();
  364. // We are running, no tasks are being processed even we just updated the
  365. // throttle up and we got no errors.
  366. // 1. We are paused and releasing tasks
  367. // 2. We are done
  368. if (self._running.value && self._isProcessing.value === 0 && err !== null) {
  369. // We have no tasks processing so this queue is now releasing resources
  370. // this could be that the queue is paused or stopped, in that case the
  371. // self.invocations._length would be > 0
  372. // If on the other hand the self.invocations._length is 0 then we have no more
  373. // tasks in the queue so the queue has ended
  374. self.onRelease(self.invocations._length);
  375. if (!self.invocations._length) { // !self._paused.value &&
  376. // Check if queue is done working
  377. // Stop the queue
  378. self._running.set(false);
  379. // self.invocations.reset(); // This should be implicit
  380. self.onEnded();
  381. }
  382. }
  383. };
  384. /** @callback done
  385. * @param {Meteor.Error | Error | String | null} [feedback] This allows the task to communicate with the queue
  386. *
  387. * Explaination of `feedback`
  388. * * `Meteor.Error` This means that the task failed in a controlled manner and is allowed to rerun
  389. * * `Error` This will throw the passed error - as its an unitended error
  390. * * `null` The task is not done yet, rerun later
  391. * * `String` The task can perform certain commands on the queue
  392. * * "pause" - pause the queue
  393. * * "stop" - stop the queue
  394. * * "reset" - reset the queue
  395. * * "cancel" - cancel the queue
  396. *
  397. */
  398. /** @method PowerQueue.prototype.runTaskDone
  399. * @private
  400. * @param {Meteor.Error | Error | String | null} [feedback] This allows the task to communicate with the queue
  401. * @param {object} invocation
  402. *
  403. * > Note: `feedback` is explained in [Done callback](#done)
  404. *
  405. */
  406. // Rig the callback function
  407. PowerQueue.prototype.runTaskDone = function(feedback, invocation) {
  408. var self = this;
  409. // If the task handler throws an error then add it to the queue again
  410. // we allow this for a max of self._maxFailures
  411. // If the error is null then we add the task silently back into the
  412. // microQueue in reverse... This could be due to pause or throttling
  413. if (feedback instanceof Meteor.Error) {
  414. // We only count failures if maxFailures are above 0
  415. if (self._maxFailures.value > 0) invocation.failures++;
  416. self._failures.inc();
  417. // If the user has set the debug flag we print out failures/errors
  418. self.debug && console.error('Error: "' + self.title + '" ' + feedback.message + ', ' + feedback.stack);
  419. if (invocation.failures < self._maxFailures.value) {
  420. // Add the task again with the increased failures
  421. self.add(invocation.data, invocation.failures, invocation._id);
  422. } else {
  423. self._errors.inc();
  424. self.errorHandler(invocation.data, self.add, invocation.failures);
  425. }
  426. // If a error is thrown we assume its not intended
  427. } else if (feedback instanceof Error) throw feedback;
  428. if (feedback)
  429. // We use null to throttle pauseable tasks
  430. if (feedback === null) {
  431. // We add this task into the queue, no questions asked
  432. self.invocations.insert(invocation._id, { data: invocation.data, failures: invocation.failures, _id: invocation._id });
  433. }
  434. // If the user returns a string we got a command
  435. if (feedback === ''+feedback) {
  436. var command = {
  437. 'pause': function() { self.pause(); },
  438. 'stop': function() { self.stop(); },
  439. 'reset': function() { self.reset(); },
  440. 'cancel': function() { self.cancel(); },
  441. };
  442. if (typeof command[feedback] === 'function') {
  443. // Run the command on this queue
  444. command[feedback]();
  445. } else {
  446. // We dont recognize this command, throw an error
  447. throw new Error('Unknown queue command "' + feedback + '"');
  448. }
  449. }
  450. // Decrease the number of tasks being processed
  451. // make sure we dont go below 0
  452. if (self._isProcessing.value > 0) self._isProcessing.dec();
  453. // Task has ended we remove the task from the process list
  454. self._processList.remove(invocation._id);
  455. invocation.data = null;
  456. invocation.failures = null;
  457. invocation._id = null;
  458. invocation = null;
  459. delete invocation;
  460. // Next task
  461. Meteor.setTimeout(function() {
  462. self.next();
  463. }, 0);
  464. };
  465. /** @method PowerQueue.prototype.runTask
  466. * @private // This is not part of the open api
  467. * @param {object} invocation The object stored in the micro-queue
  468. */
  469. PowerQueue.prototype.runTask = function(invocation) {
  470. var self = this;
  471. // We start the fitting task handler
  472. // Currently we only support the PowerQueue but we could have a more general
  473. // interface for tasks that allow throttling
  474. try {
  475. if (invocation.data instanceof PowerQueue) {
  476. // Insert PowerQueue into process list
  477. self._processList.insert(invocation._id, { id: invocation._id, queue: invocation.data });
  478. // Handle task
  479. self.queueTaskHandler(invocation.data, function subQueueCallbackDone(feedback) {
  480. self.runTaskDone(feedback, invocation);
  481. }, invocation.failures);
  482. } else {
  483. // Insert task into process list
  484. self._processList.insert(invocation._id, invocation.data);
  485. // Handle task
  486. self.taskHandler(invocation.data, function taskCallbackDone(feedback) {
  487. self.runTaskDone(feedback, invocation);
  488. }, invocation.failures);
  489. }
  490. } catch(err) {
  491. throw new Error('Error while running taskHandler for queue, Error: ' + err.message);
  492. }
  493. };
  494. /** @method PowerQueue.prototype.queueTaskHandler
  495. * This method handles tasks that are sub queues
  496. */
  497. PowerQueue.prototype.queueTaskHandler = function(subQueue, next, failures) {
  498. var self = this;
  499. // Monitor sub queue task releases
  500. subQueue.onRelease = function(remaining) {
  501. // Ok, we were paused - this could be throttling so we respect this
  502. // So when the queue is halted we add it back into the main queue
  503. if (remaining > 0) {
  504. // We get out of the queue but dont repport error and add to run later
  505. next(null);
  506. } else {
  507. // Queue has ended
  508. // We simply trigger next task when the sub queue is complete
  509. next();
  510. // When running subqueues it doesnt make sense to track failures and retry
  511. // the sub queue - this is sub queue domain
  512. }
  513. };
  514. // Start the queue
  515. subQueue.run();
  516. };
  517. /** @callback PowerQueue.prototype.taskHandler
  518. * @param {any} data This can be data or functions
  519. * @param {function} next Function `next` call this to end task
  520. * @param {number} failures Number of failures on this task
  521. *
  522. * Default task handler expects functions as data:
  523. * ```js
  524. * self.taskHandler = function(data, next, failures) {
  525. * // This default task handler expects invocation to be a function to run
  526. * if (typeof data !== 'function') {
  527. * throw new Error('Default task handler expects a function');
  528. * }
  529. * try {
  530. * // Have the function call next
  531. * data(next, failures);
  532. * } catch(err) {
  533. * // Throw to fail this task
  534. * next(err);
  535. * }
  536. * };
  537. * ```
  538. */
  539. // Can be overwrittin by the user
  540. PowerQueue.prototype.taskHandler = function(data, next, failures) {
  541. var self = this;
  542. // This default task handler expects invocation to be a function to run
  543. if (typeof data !== 'function') {
  544. throw new Error('Default task handler expects a function');
  545. }
  546. try {
  547. // Have the function call next
  548. data(next, failures);
  549. } catch(err) {
  550. // Throw to fail this task
  551. next(err);
  552. }
  553. };
  554. /** @callback PowerQueue.prototype.errorHandler
  555. * @param {any} data This can be data or functions
  556. * @param {function} addTask Use this function to insert the data into the queue again
  557. * @param {number} failures Number of failures on this task
  558. *
  559. * The default callback:
  560. * ```js
  561. * var foo = new PowerQueue();
  562. *
  563. * // Overwrite the default action
  564. * foo.errorHandler = function(data, addTask, failures) {
  565. * // This could be overwritten the data contains the task data and addTask
  566. * // is a helper for adding the task to the queue
  567. * // try again: addTask(data);
  568. * // console.log('Terminate at ' + failures + ' failures');
  569. * };
  570. * ```
  571. */
  572. PowerQueue.prototype.errorHandler = function(data, addTask, failures) {
  573. var self = this;
  574. // This could be overwritten the data contains the task data and addTask
  575. // is a helper for adding the task to the queue
  576. // try again: addTask(data);
  577. self.debug && console.log('Terminate at ' + failures + ' failures');
  578. };
  579. /** @method PowerQueue.prototype.pause Pause the queue
  580. * @todo We should have it pause all processing tasks
  581. */
  582. PowerQueue.prototype.pause = function() {
  583. var self = this;
  584. if (!self._paused.value) {
  585. self._paused.set(true);
  586. // Loop through the processing tasks and pause these
  587. self._processList.forEach(function(data) {
  588. if (data.queue instanceof PowerQueue) {
  589. // Pause the sub queue
  590. data.queue.pause();
  591. }
  592. }, true);
  593. // Trigger callback
  594. self.onPaused();
  595. }
  596. };
  597. /** @method PowerQueue.prototype.resume Start a paused queue
  598. * @todo We should have it resume all processing tasks
  599. *
  600. * > This will not start a stopped queue
  601. */
  602. PowerQueue.prototype.resume = function() {
  603. var self = this;
  604. self.run();
  605. };
  606. /** @method PowerQueue.prototype.run Starts the queue
  607. * > Using this command will resume a paused queue and will
  608. * > start a stopped queue.
  609. */
  610. PowerQueue.prototype.run = function() {
  611. var self = this;
  612. //not paused and already running or queue empty or paused subqueues
  613. if (!self._paused.value && self._running.value || !self.invocations._length) {
  614. return;
  615. }
  616. self._paused.set(false);
  617. self._running.set(true);
  618. self.next(null);
  619. };
  620. /** @method PowerQueue.prototype.stop Stops the queue
  621. */
  622. PowerQueue.prototype.stop = function() {
  623. var self = this;
  624. self._running.set(false);
  625. };
  626. /** @method PowerQueue.prototype.cancel Cancel the queue
  627. */
  628. PowerQueue.prototype.cancel = function() {
  629. var self = this;
  630. self.reset();
  631. };