pax_global_header00006660000000000000000000000064130547047460014524gustar00rootroot0000000000000052 comment=3c315b738d0578c2c54be2beb0469d00ccf1dc25 run-queue-1.0.3/000077500000000000000000000000001305470474600134535ustar00rootroot00000000000000run-queue-1.0.3/.gitignore000066400000000000000000000000511305470474600154370ustar00rootroot00000000000000coverage .nyc_output *~ .#* node_modules run-queue-1.0.3/.travis.yml000066400000000000000000000001121305470474600155560ustar00rootroot00000000000000language: node_js sudo: false node_js: - "7" - "6" - "4" - "0.12" run-queue-1.0.3/README.md000066400000000000000000000043221305470474600147330ustar00rootroot00000000000000# run-queue A promise based, dynamic priority queue runner, with concurrency limiting. ```js const RunQueue = require('run-queue') const queue = new RunQueue({ maxConcurrency: 1 }) queue.add(1, example, [-1]) for (let ii = 0; ii < 5; ++ii) { queue.add(0, example, [ii]) } const finished = [] queue.run().then( console.log(finished) }) function example (num, next) { setTimeout(() => { finished.push(num) next() }, 5 - Math.abs(num)) } ``` would output ``` [ 0, 1, 2, 3, 4, -1 ] ``` If you bump concurrency to `2`, then you get: ``` [ 1, 0, 3, 2, 4, -1 ] ``` The concurrency means that they don't finish in order, because some take longer than others. Each priority level must finish entirely before the next priority level is run. See [PRIORITIES](https://github.com/iarna/run-queue#priorities) below. This is even true if concurrency is set high enough that all of the regular queue can execute at once, for instance, with `maxConcurrency: 10`: ``` [ 4, 3, 2, 1, 0, -1 ] ``` ## API ### const queue = new RunQueue(options) Create a new queue. Options may contain: * maxConcurrency - (Default: `1`) The maximum number of jobs to execute at once. * Promise - (Default: global.Promise) The promise implementation to use. ### queue.add (prio, fn, args) Add a new job to the end of the queue at priority `prio` that will run `fn` with `args`. If `fn` is async then it should return a Promise. ### queue.run () Start running the job queue. Returns a Promise that resolves when either all the jobs are complete or a job ends in error (throws or returns a rejected promise). If a job ended in error then this Promise will be rejected with that error and no further queue running will be done. ## PRIORITIES Priorities are any integer value >= 0. Lowest is executed first. Priorities essentially represent distinct job queues. All jobs in a queue must complete before the next highest priority job queue is executed. This means that if you have two queues, `0` and `1` then ALL jobs in `0` must complete before ANY execute in `1`. If you add new `0` level jobs while `1` level jobs are running then it will switch back processing the `0` queue and won't execute any more `1` jobs till all of the new `0` jobs complete. run-queue-1.0.3/package.json000066400000000000000000000013621305470474600157430ustar00rootroot00000000000000{ "name": "run-queue", "version": "1.0.3", "description": "A promise based, dynamic priority queue runner, with concurrency limiting.", "main": "queue.js", "scripts": { "test": "standard && tap -J test" }, "keywords": [], "author": "Rebecca Turner (http://re-becca.org/)", "license": "ISC", "devDependencies": { "standard": "^8.6.0", "tap": "^10.2.0" }, "files": [ "queue.js" ], "directories": { "test": "test" }, "dependencies": { "aproba": "^1.1.1" }, "repository": { "type": "git", "url": "git+https://github.com/iarna/run-queue.git" }, "bugs": { "url": "https://github.com/iarna/run-queue/issues" }, "homepage": "https://npmjs.com/package/run-queue" } run-queue-1.0.3/queue.js000066400000000000000000000052731305470474600151440ustar00rootroot00000000000000'use strict' module.exports = RunQueue var validate = require('aproba') function RunQueue (opts) { validate('Z|O', [opts]) if (!opts) opts = {} this.finished = false this.inflight = 0 this.maxConcurrency = opts.maxConcurrency || 1 this.queued = 0 this.queue = [] this.currentPrio = null this.currentQueue = null this.Promise = opts.Promise || global.Promise this.deferred = {} } RunQueue.prototype = {} RunQueue.prototype.run = function () { if (arguments.length !== 0) throw new Error('RunQueue.run takes no arguments') var self = this var deferred = this.deferred if (!deferred.promise) { deferred.promise = new this.Promise(function (resolve, reject) { deferred.resolve = resolve deferred.reject = reject self._runQueue() }) } return deferred.promise } RunQueue.prototype._runQueue = function () { var self = this while ((this.inflight < this.maxConcurrency) && this.queued) { if (!this.currentQueue || this.currentQueue.length === 0) { // wait till the current priority is entirely processed before // starting a new one if (this.inflight) return var prios = Object.keys(this.queue) for (var ii = 0; ii < prios.length; ++ii) { var prioQueue = this.queue[prios[ii]] if (prioQueue.length) { this.currentQueue = prioQueue this.currentPrio = prios[ii] break } } } --this.queued ++this.inflight var next = this.currentQueue.shift() var args = next.args || [] // we explicitly construct a promise here so that queue items can throw // or immediately return to resolve var queueEntry = new this.Promise(function (resolve) { resolve(next.cmd.apply(null, args)) }) queueEntry.then(function () { --self.inflight if (self.finished) return if (self.queued <= 0 && self.inflight <= 0) { self.finished = true self.deferred.resolve() } self._runQueue() }, function (err) { self.finished = true self.deferred.reject(err) }) } } RunQueue.prototype.add = function (prio, cmd, args) { if (this.finished) throw new Error("Can't add to a finished queue. Create a new queue.") if (Math.abs(Math.floor(prio)) !== prio) throw new Error('Priorities must be a positive integer value.') validate('NFA|NFZ', [prio, cmd, args]) prio = Number(prio) if (!this.queue[prio]) this.queue[prio] = [] ++this.queued this.queue[prio].push({cmd: cmd, args: args}) // if this priority is higher than the one we're currently processing, // switch back to processing its queue. if (this.currentPrio > prio) { this.currentQueue = this.queue[prio] this.currentPrio = prio } } run-queue-1.0.3/test/000077500000000000000000000000001305470474600144325ustar00rootroot00000000000000run-queue-1.0.3/test/queue.js000066400000000000000000000033131305470474600161140ustar00rootroot00000000000000'use strict' var test = require('tap').test var RunQueue = require('../queue.js') test('concur', function (t) { t.plan(7) var queue1 = new RunQueue({ maxConcurrency: 1 }) var finished1 = [] setup(queue1, finished1) var queue1complete = queue1.run() t.is(queue1.run(), queue1complete, 'multiple run calls get the same promise') queue1complete.then(function () { t.isDeeply(finished1, [ 0, 1, 2, 3, 4, -1 ], 'concurrency 1') t.is(queue1.run(), queue1complete, 'multiple run calls get the same promise, even after completion') }) var queue10 = new RunQueue({ maxConcurrency: 10 }) var finished10 = [] setup(queue10, finished10) queue10.run().then(function () { t.isDeeply(finished10, [ 4, 3, 2, 1, 0, -1 ], 'concurrency 10') }) var queueE = new RunQueue({ maxConcurrency: 3 }) queueE.add(0, function () { throw new Error('STOP!') }) var finishedE = [] setup(queueE, finishedE) queueE.run().then(function () { t.fail('got early error') t.fail() t.fail() }, function (err) { t.is(err && err.message, 'STOP!', 'got early error') t.isDeeply(finishedE, [ ], 'no results on early error') try { queueE.add(0, function () {}, []) t.fail('throw on add to finished queue') } catch (ex) { t.pass('throw on add to finished queue') } }) }) function setup (queue, finished) { queue.add(1, example(finished), [-1]) for (var ii = 0; ii < 5; ++ii) { queue.add(0, example(finished), [ii]) } } function example (finished) { return function (num) { return new Promise(function (resolve) { setTimeout(function () { finished.push(num) resolve() }, (5 - Math.abs(num)) * 10) }) } }