├── .gitignore ├── .editorconfig ├── SECURITY.md ├── package.json ├── .github └── workflows │ └── test.yml ├── LICENSE-MIT ├── lib ├── queue.d.ts ├── subqueue.js └── queue.js ├── README.md └── test └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules 3 | .project 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Security contact information 2 | 3 | To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. 4 | 5 | You can also send an email to admin@simonboudrias.com for direct contact with the maintainer. 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grouped-queue", 3 | "version": "2.1.0", 4 | "description": "In memory queue system prioritizing tasks", 5 | "types": "lib/queue.d.ts", 6 | "main": "lib/queue.js", 7 | "scripts": { 8 | "test": "mocha -R spec", 9 | "lint": "npx oxlint && npx prettier --check ." 10 | }, 11 | "repository": "SBoudrias/grouped-queue", 12 | "keywords": [ 13 | "queue", 14 | "async", 15 | "task", 16 | "flow", 17 | "control" 18 | ], 19 | "author": "Simon Boudrias ", 20 | "license": "MIT", 21 | "files": [ 22 | "lib" 23 | ], 24 | "engines": { 25 | "node": ">=8.0.0" 26 | }, 27 | "devDependencies": { 28 | "mocha": "^11.7.1", 29 | "oxlint": "^1.12.0", 30 | "prettier": "^3.6.2", 31 | "sinon": "^21.0.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | name: Testing - Node ${{ matrix.node-version }} ${{ matrix.os }} 8 | runs-on: ${{ matrix.os }} 9 | 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | node-version: [20.x, 22.x, 24.x] 14 | os: [ubuntu-latest, windows-latest, macos-latest] 15 | 16 | steps: 17 | - uses: actions/checkout@v5 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - run: npm ci 22 | - run: npm test 23 | 24 | lint: 25 | name: Linting 26 | runs-on: macos-latest 27 | 28 | steps: 29 | - uses: actions/checkout@v5 30 | - uses: actions/setup-node@v4 31 | with: 32 | node-version: latest 33 | - run: npm ci 34 | - run: npm run lint 35 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Simon Boudrias 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/queue.d.ts: -------------------------------------------------------------------------------- 1 | import type event from "node:events"; 2 | 3 | /** 4 | * 5 | */ 6 | declare class GroupedQueue extends event.EventEmitter { 7 | queueNames: string[]; 8 | 9 | constructor(subQueueNames: string[], runOnAdd: boolean); 10 | 11 | /** 12 | * Adds a sub-queue. 13 | */ 14 | addSubQueue(name: string, before?: string): void; 15 | 16 | /** 17 | * Stop running tasks. 18 | */ 19 | pause(): void; 20 | 21 | /** 22 | * Add a task into a group queue 23 | * 24 | * @param subQueueName - The name of the sub-queue. 25 | * @param task - The task to add. 26 | * @param [options] - Options for the task. 27 | * @param [options.once] - If specified, the task will only be added if there is no other task with the same `once` value in the queue. 28 | * @param [options.run] - The queue will start running immediately. Defaults to runOnAdd. 29 | */ 30 | add( 31 | subQueueName: string, 32 | task: (done: () => void, stop: (error: Error) => void) => Promise, 33 | options?: { once?: string; run?: boolean }, 34 | ): void; 35 | 36 | /** 37 | * Run tasks in the queue. 38 | */ 39 | run(): void; 40 | 41 | /** 42 | * Schedule `run()`. 43 | */ 44 | start(): void; 45 | } 46 | 47 | export = GroupedQueue; 48 | -------------------------------------------------------------------------------- /lib/subqueue.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | module.exports = SubQueue; 3 | 4 | function isPromise(obj) { 5 | return ( 6 | !!obj && 7 | (typeof obj === "object" || typeof obj === "function") && 8 | typeof obj.then === "function" 9 | ); 10 | } 11 | 12 | function SubQueue() { 13 | this.__queue__ = []; 14 | } 15 | 16 | /** 17 | * Add a task to this queue 18 | * @param {Function} task 19 | */ 20 | 21 | SubQueue.prototype.push = function (task, opt) { 22 | opt = opt || {}; 23 | 24 | // Don't register named task if they're already planned 25 | if (opt.once && this.__queue__.find((queue) => queue.name === opt.once)) { 26 | return; 27 | } 28 | 29 | this.__queue__.push({ task: task, name: opt.once }); 30 | }; 31 | 32 | /** 33 | * Return the first entry of this queue 34 | * @return {Function} The first task 35 | */ 36 | 37 | SubQueue.prototype.shift = function () { 38 | return this.__queue__.shift(); 39 | }; 40 | 41 | /** 42 | * Run task 43 | * @param {Function} skip Callback if no task is available 44 | * @param {Function} done Callback once the task is completed 45 | */ 46 | 47 | SubQueue.prototype.run = function (skip, done, stop) { 48 | if (this.__queue__.length === 0) return skip(); 49 | setImmediate(() => { 50 | var result; 51 | try { 52 | result = this.shift().task.call(null, done, stop); 53 | } catch (error) { 54 | stop(error); 55 | } 56 | if (isPromise(result)) { 57 | result.catch((error) => stop(error)); 58 | } 59 | }); 60 | }; 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Grouped Queue 2 | 3 | In memory queue system prioritizing tasks. 4 | 5 | # Documentation 6 | 7 | ## Installation 8 | 9 | ```bash 10 | npm install --save grouped-queue 11 | ``` 12 | 13 | ## Methods 14 | 15 | ### Constructor 16 | 17 | The constructor takes an optional array of task groups. The first `String` name will be the first queue to be emptied, the second string will be the second group emptied, etc. 18 | 19 | By default, the constructor will always add a `default` queue in the last position. You can overwrite the position of the `default` group if you specify it explicitly. 20 | 21 | ```javascript 22 | import Queue from "grouped-queue"; 23 | 24 | const queue = new Queue(["first", "second", "third"]); 25 | ``` 26 | 27 | ### Queue#add `add( [group], task, [options] )` 28 | 29 | Add a task into a group queue. If no group name is specified, `default` will be used. 30 | 31 | Implicitly, each time you add a task, the queue will start emptying (if not already running). 32 | 33 | Each task function is passed a callback function. This callback must be called when the task is complete. 34 | 35 | ```javascript 36 | queue.add((cb) => { 37 | DB.fetch().then(cb); 38 | }); 39 | ``` 40 | 41 | #### Option: `once` 42 | 43 | You can register tasks in queues that will be dropped if they're already planned. This is done with the `once` option. You pass a String (basically a name) to the `once` option. 44 | 45 | ```javascript 46 | // This one will eventually run 47 | queue.add(method, { once: "readDB" }); 48 | 49 | // This one will be dropped as `method` is currently in the queue 50 | queue.add(method3, { once: "readDB" }); 51 | ``` 52 | 53 | #### Option: `run` 54 | 55 | You can register a task without launching the run loop by passing the argument `run: false`. 56 | 57 | ```javascript 58 | queue.add(method, { run: false }); 59 | ``` 60 | 61 | ### Delaying runs 62 | 63 | If you don't want tasks to run as they're added, you can hold the queue until manually starting. 64 | 65 | ```javascript 66 | const queue = new Queue( 67 | ["first", "second", "third"], 68 | // `runOnAdd` option; this boolean instruct the queue to not auto-start. 69 | false, 70 | ); 71 | 72 | // Later on, to start processing 73 | queue.start(); 74 | 75 | // And pause to stop processing new tasks; running task will complete. 76 | queue.pause(); 77 | ``` 78 | 79 | ## Events 80 | 81 | ### `end` 82 | 83 | This event is called **each time** the queue emptied itself. 84 | 85 | ```javascript 86 | queue.on("end", () => { 87 | console.log("done!"); 88 | }); 89 | ``` 90 | 91 | # Contributing 92 | 93 | - **Unit test**: Unit tests are written in Mocha. Please add a unit test for every new feature 94 | or bug fix. `npm test` to run the test suite. 95 | - **Documentation**: Add documentation for every API change. Feel free to send corrections 96 | or better docs! 97 | - **Pull Requests**: Send _fixes_ PR on the `master` branch. 98 | 99 | # License 100 | 101 | Copyright (c) 2013 Simon Boudrias (twitter: @vaxilart) 102 | Licensed under the MIT license. 103 | -------------------------------------------------------------------------------- /lib/queue.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var util = require("util"); 3 | var events = require("events"); 4 | var SubQueue = require("./subqueue"); 5 | 6 | module.exports = Queue; 7 | 8 | /** 9 | * Queue constructor 10 | * @param {String[]} [subQueue] The order of the sub-queues. First one will be runned first. 11 | */ 12 | 13 | function Queue(subQueues, runOnAdd = true) { 14 | subQueues = subQueues || []; 15 | if (!subQueues.includes("default")) { 16 | subQueues = subQueues.concat(["default"]); 17 | } 18 | subQueues = Array.from(new Set(subQueues)); 19 | 20 | this.queueNames = subQueues; 21 | this.__queues__ = {}; 22 | this.runOnAdd = runOnAdd; 23 | 24 | subQueues.forEach( 25 | function (name) { 26 | this.__queues__[name] = new SubQueue(); 27 | }.bind(this), 28 | ); 29 | } 30 | 31 | util.inherits(Queue, events.EventEmitter); 32 | 33 | /** 34 | * Create a new sub-queue. 35 | * @param {String} name The sub-queue to create 36 | * @param {String} [before] Add the new sub-queue before the this sub-queue. 37 | * Otherwise the new sub-queue will be added last. 38 | */ 39 | 40 | Queue.prototype.addSubQueue = function (name, before) { 41 | if (this.__queues__[name]) { 42 | // Sub-queue already exists 43 | return; 44 | } 45 | 46 | if (!before) { 47 | // Add at last place. 48 | this.queueNames.push(name); 49 | this.__queues__[name] = new SubQueue(); 50 | return; 51 | } 52 | 53 | if (!this.__queues__[before] || this.queueNames.indexOf(before) === -1) { 54 | throw new Error("sub-queue " + before + " not found"); 55 | } 56 | 57 | // Add new sub-queue into the array. 58 | this.queueNames.splice(this.queueNames.indexOf(before), 0, name); 59 | this.__queues__[name] = new SubQueue(); 60 | }; 61 | 62 | /** 63 | * Add a task to a queue. 64 | * @param {String} [name='default'] The sub-queue to append the task 65 | * @param {Function} task 66 | * @param {Object} [opt] Options hash 67 | * @param {String} [opt.once] If a task with the same `once` value is inside the 68 | * queue, don't add this task. 69 | * @param {Boolean} [opt.run] If `run` is false, don't run the task. 70 | */ 71 | 72 | Queue.prototype.add = function (name, task, opt) { 73 | if (typeof name !== "string") { 74 | opt = task; 75 | task = name; 76 | name = "default"; 77 | } 78 | 79 | this.__queues__[name].push(task, opt); 80 | 81 | // don't run the tasks if `opt.run` is false 82 | var run = (opt || {}).run; 83 | run = run === undefined ? this.runOnAdd : run; 84 | if (!run) return; 85 | this.start(); 86 | }; 87 | 88 | /** 89 | * Schedule run() to be executed. 90 | */ 91 | 92 | Queue.prototype.start = function () { 93 | if (this.running) return; 94 | 95 | setImmediate(this.run.bind(this)); 96 | }; 97 | 98 | /** 99 | * Start emptying the queues 100 | * Tasks are always run from the higher priority queue down to the lowest. After each 101 | * task complete, the process is re-runned from the first queue until a task is found. 102 | * 103 | * Tasks are passed a `callback` method which should be called once the task is over. 104 | */ 105 | 106 | Queue.prototype.run = function () { 107 | if (this.running) return; 108 | 109 | this.running = true; 110 | this._exec( 111 | function () { 112 | this.running = false; 113 | if ( 114 | Object.values(this.__queues__).find( 115 | (queue) => queue.__queue__.length > 0, 116 | ) === undefined 117 | ) { 118 | this.emit("end"); 119 | } else { 120 | this.emit("paused"); 121 | } 122 | }.bind(this), 123 | function (error) { 124 | this.running = false; 125 | if (error) { 126 | this.emit("error", error); 127 | } else { 128 | this.running = false; 129 | this.emit("paused"); 130 | } 131 | }.bind(this), 132 | ); 133 | }; 134 | 135 | /** 136 | * Pause the queue 137 | */ 138 | 139 | Queue.prototype.pause = function () { 140 | this.running = false; 141 | }; 142 | 143 | Queue.prototype._exec = function (done, stop) { 144 | var pointer = -1; 145 | var names = this.queueNames; 146 | 147 | var next = function next() { 148 | if (!this.running) return done(); 149 | 150 | pointer++; 151 | if (pointer >= names.length) return done(); 152 | this.__queues__[names[pointer]].run( 153 | next.bind(this), 154 | this._exec.bind(this, done, stop), 155 | stop, 156 | ); 157 | }.bind(this); 158 | 159 | next(); 160 | }; 161 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var assert = require("assert"); 3 | var sinon = require("sinon"); 4 | var Queue = require("../lib/queue"); 5 | var SubQueue = require("../lib/subqueue"); 6 | 7 | describe("Queue", function () { 8 | beforeEach(function () { 9 | var runOrder = (this.runOrder = []); 10 | this.q = new Queue(["before", "run", "after"]); 11 | this.task1 = function (cb) { 12 | runOrder.push("task1"); 13 | cb(); 14 | }; 15 | this.task2 = function (cb) { 16 | runOrder.push("task2"); 17 | cb(); 18 | }; 19 | var callbackError = (this.callbackError = new Error()); 20 | this.taskCallError = function (cb, stop) { 21 | runOrder.push("taskCallError"); 22 | stop(callbackError); 23 | }; 24 | var throwError = (this.throwError = new Error()); 25 | this.taskThrowError = function () { 26 | runOrder.push("taskThrowError"); 27 | throw throwError; 28 | }; 29 | var rejectError = (this.rejectError = new Error()); 30 | this.taskRejectError = function () { 31 | runOrder.push("taskRejectError"); 32 | return Promise.reject(rejectError); 33 | }; 34 | }); 35 | 36 | describe("Constructor", function () { 37 | it("create sub-queues", function () { 38 | assert(this.q.__queues__.before instanceof SubQueue); 39 | assert(this.q.__queues__.run instanceof SubQueue); 40 | assert(this.q.__queues__.after instanceof SubQueue); 41 | }); 42 | 43 | it("create a default queue", function () { 44 | assert(this.q.__queues__.default instanceof SubQueue); 45 | assert.equal(Object.keys(this.q.__queues__).pop(), "default"); 46 | }); 47 | 48 | it("allow redefining `default` queue position", function () { 49 | var queue = new Queue(["before", "default", "after"]); 50 | assert.deepEqual(Object.keys(queue.__queues__), [ 51 | "before", 52 | "default", 53 | "after", 54 | ]); 55 | }); 56 | 57 | it("does not mutate the arguments", function () { 58 | const defaultArr = ["before"]; 59 | var queue = new Queue(defaultArr); 60 | assert.deepEqual(Object.keys(queue.__queues__), ["before", "default"]); 61 | assert.deepEqual(defaultArr, ["before"]); 62 | }); 63 | }); 64 | 65 | describe("#add", function () { 66 | beforeEach(function () { 67 | this.runStub = sinon.stub(this.q, "run"); 68 | }); 69 | 70 | it("add task to a queue", function () { 71 | this.q.add("before", this.task1); 72 | this.q.add("after", this.task2); 73 | assert.equal(this.q.__queues__.before.shift().task, this.task1); 74 | assert.equal(this.q.__queues__.after.shift().task, this.task2); 75 | }); 76 | 77 | it("default task in the default queue", function () { 78 | this.q.add(this.task1); 79 | assert.equal(this.q.__queues__.default.shift().task, this.task1); 80 | }); 81 | 82 | it("calls run", function (done) { 83 | this.q.add(this.task1); 84 | this.q.add(this.task2); 85 | setImmediate( 86 | function () { 87 | assert(this.runStub.called); 88 | done(); 89 | }.bind(this), 90 | ); 91 | }); 92 | 93 | it("does not call run", function (done) { 94 | this.q.add("before", this.task1, { run: false }); 95 | this.q.add("after", this.task2, { run: false }); 96 | setImmediate( 97 | function () { 98 | assert.equal(this.runStub.called, false); 99 | done(); 100 | }.bind(this), 101 | ); 102 | }); 103 | 104 | it("only run named task once", function () { 105 | this.q.add(this.task1, { once: "done" }); 106 | this.q.add(this.task2, { once: "done" }); 107 | assert.equal(this.q.__queues__.default.__queue__.length, 1); 108 | }); 109 | 110 | it("runs priority in order", function (done) { 111 | this.runStub.restore(); 112 | this.q.add("after", this.task1); 113 | this.q.add("before", this.task2); 114 | this.q.once( 115 | "end", 116 | function () { 117 | assert.deepEqual(this.runOrder, ["task2", "task1"]); 118 | done(); 119 | }.bind(this), 120 | ); 121 | }); 122 | }); 123 | 124 | describe("#run", function () { 125 | it("run the queue by default", function (done) { 126 | this.q.add(function () { 127 | done(); 128 | }); 129 | }); 130 | 131 | it("run the queue with runOnAdd set to false and opt is undefined", function (done) { 132 | var q = new Queue(["before", "run", "after"], false); 133 | q.add(function () { 134 | done(); 135 | }); 136 | done(); 137 | }); 138 | 139 | it("run the queue with runOnAdd set to false and opt is defined", function (done) { 140 | var q = new Queue(["before", "run", "after"], false); 141 | q.add(function () { 142 | done(); 143 | }, {}); 144 | done(); 145 | }); 146 | 147 | it('run task in "First-in First-out" order', function (done) { 148 | this.q.add(this.task2); 149 | this.q.add(this.task1); 150 | this.q.once( 151 | "end", 152 | function () { 153 | assert.equal(this.runOrder[0], "task2"); 154 | assert.equal(this.runOrder[1], "task1"); 155 | done(); 156 | }.bind(this), 157 | ); 158 | }); 159 | 160 | it("run async tasks", function (done) { 161 | var counter = 0; 162 | this.q.add(function (cb) { 163 | assert.equal((counter += 1), 1); 164 | cb(); 165 | }); 166 | this.q.add(function () { 167 | assert.equal((counter += 1), 2); 168 | done(); 169 | }); 170 | }); 171 | 172 | it("emits error on rejected tasks", function (done) { 173 | var self = this; 174 | this.q.add(function () { 175 | return Promise.reject(new Error("errorTask")); 176 | }); 177 | var onError = function () { 178 | assert(!self.q.running); 179 | self.q.removeListener("error", onError); 180 | done(); 181 | }; 182 | this.q.on("error", onError); 183 | }); 184 | 185 | it("emits error on thrown tasks", function (done) { 186 | var self = this; 187 | this.q.add(function () { 188 | throw new Error("errorTask"); 189 | }); 190 | var onError = function () { 191 | assert(!self.q.running); 192 | self.q.removeListener("error", onError); 193 | done(); 194 | }; 195 | this.q.on("error", onError); 196 | }); 197 | 198 | it("run prioritized tasks first", function (done) { 199 | var stub = sinon.stub(this.q, "run"); 200 | this.q.add("after", this.task2); 201 | this.q.add("before", this.task1); 202 | stub.restore(); 203 | this.q.run(); 204 | this.q.once( 205 | "end", 206 | function () { 207 | assert.equal(this.runOrder[0], "task1"); 208 | assert.equal(this.runOrder[1], "task2"); 209 | done(); 210 | }.bind(this), 211 | ); 212 | }); 213 | 214 | it("always re-exec from the first queue down", function (done) { 215 | this.q.add( 216 | function (cb) { 217 | this.q.add("after", this.task1); 218 | this.q.add("before", this.task2); 219 | cb(); 220 | this.q.once( 221 | "end", 222 | function () { 223 | assert.equal(this.runOrder[0], "task2"); 224 | assert.equal(this.runOrder[1], "task1"); 225 | done(); 226 | }.bind(this), 227 | ); 228 | }.bind(this), 229 | ); 230 | }); 231 | 232 | it("run the queues explicitly after tasks are added", function (done) { 233 | this.q.add("before", this.task1, { run: false }); 234 | this.q.add("after", this.task2, { run: false }); 235 | this.q.run(); 236 | this.q.once( 237 | "end", 238 | function () { 239 | assert.equal(this.runOrder[0], "task1"); 240 | assert.equal(this.runOrder[1], "task2"); 241 | done(); 242 | }.bind(this), 243 | ); 244 | }); 245 | 246 | it("add new subqueue and run tasks", function (done) { 247 | this.q.add("before", this.task1, { run: false }); 248 | this.q.add("after", this.task2, { run: false }); 249 | 250 | var self = this; 251 | var task3 = function (cb) { 252 | self.runOrder.push("task3"); 253 | cb(); 254 | }; 255 | var task4 = function (cb) { 256 | self.runOrder.push("task4"); 257 | cb(); 258 | }; 259 | var task5 = function (cb) { 260 | self.runOrder.push("task5"); 261 | cb(); 262 | }; 263 | var task6 = function (cb) { 264 | self.runOrder.push("task6"); 265 | cb(); 266 | }; 267 | this.q.addSubQueue("between", "after"); 268 | // Ignored 269 | this.q.addSubQueue("between"); 270 | this.q.addSubQueue("init", "before"); 271 | this.q.addSubQueue("after-end"); 272 | this.q.add("init", task3, { run: false }); 273 | this.q.add("before", task4, { run: false }); 274 | this.q.add("between", task5, { run: false }); 275 | this.q.add("after-end", task6, { run: false }); 276 | 277 | this.q.run(); 278 | this.q.once( 279 | "end", 280 | function () { 281 | assert.equal(this.runOrder[0], "task3"); 282 | assert.equal(this.runOrder[1], "task1"); 283 | assert.equal(this.runOrder[2], "task4"); 284 | assert.equal(this.runOrder[3], "task5"); 285 | assert.equal(this.runOrder[4], "task2"); 286 | assert.equal(this.runOrder[5], "task6"); 287 | assert.equal(this.runOrder[6], undefined); 288 | done(); 289 | }.bind(this), 290 | ); 291 | }); 292 | 293 | it("run the queues from new subqueue inside task", function (done) { 294 | this.q.add("before", this.task1, { run: false }); 295 | this.q.add("after", this.task2, { run: false }); 296 | 297 | var self = this; 298 | var task4 = function (cb) { 299 | self.runOrder.push("task4"); 300 | cb(); 301 | }; 302 | var task5 = function (cb) { 303 | self.runOrder.push("task5"); 304 | cb(); 305 | }; 306 | var task6 = function (cb) { 307 | self.runOrder.push("task6"); 308 | cb(); 309 | }; 310 | var task3 = function (cb) { 311 | self.q.addSubQueue("before", "after"); 312 | self.q.addSubQueue("init", "before"); 313 | self.q.add("init", task4, { run: false }); 314 | self.q.add("before", task6, { run: false }); 315 | self.runOrder.push("task3"); 316 | cb(); 317 | }; 318 | this.q.add("before", task3, { run: false }); 319 | this.q.add("before", task5, { run: false }); 320 | 321 | this.q.run(); 322 | this.q.once( 323 | "end", 324 | function () { 325 | assert.equal(this.runOrder[0], "task1"); 326 | assert.equal(this.runOrder[1], "task3"); 327 | assert.equal(this.runOrder[2], "task4"); 328 | assert.equal(this.runOrder[3], "task5"); 329 | assert.equal(this.runOrder[4], "task6"); 330 | assert.equal(this.runOrder[5], "task2"); 331 | assert.equal(this.runOrder[6], undefined); 332 | done(); 333 | }.bind(this), 334 | ); 335 | }); 336 | 337 | it("emit `end` event once the queue is cleared.", function (done) { 338 | this.q.on("end", function () { 339 | done(); 340 | }); 341 | this.q.add("after", this.task1); 342 | }); 343 | 344 | it("pauses the queue and continue", function (done) { 345 | var self = this; 346 | var pause = function (cb) { 347 | self.runOrder.push("pause"); 348 | self.q.pause(); 349 | cb(); 350 | }; 351 | 352 | this.q.add("before", this.task1, { run: false }); 353 | this.q.add("run", pause, { run: false }); 354 | this.q.add("after", this.task2.bind(this), { run: false }); 355 | this.q.add("after", pause, { run: false }); 356 | this.q.add("after", this.task2, { run: false }); 357 | 358 | this.q.run(); 359 | this.q.on("paused", function () { 360 | self.runOrder.push("paused"); 361 | self.q.run(); 362 | }); 363 | this.q.once( 364 | "end", 365 | function () { 366 | assert.equal(this.runOrder[0], "task1"); 367 | assert.equal(this.runOrder[1], "pause"); 368 | assert.equal(this.runOrder[2], "paused"); 369 | assert.equal(this.runOrder[3], "task2"); 370 | assert.equal(this.runOrder[4], "pause"); 371 | assert.equal(this.runOrder[5], "paused"); 372 | assert.equal(this.runOrder[6], "task2"); 373 | assert.equal(this.runOrder[7], undefined); 374 | done(); 375 | }.bind(this), 376 | ); 377 | }); 378 | 379 | it("pauses the queue with pause callback", function (done) { 380 | var self = this; 381 | var pause = function (_, pause) { 382 | self.runOrder.push("pause"); 383 | pause(); 384 | }; 385 | 386 | this.q.add("before", this.task1, { run: false }); 387 | this.q.add("run", pause, { run: false }); 388 | this.q.add("after", this.task2.bind(this), { run: false }); 389 | this.q.add("after", pause, { run: false }); 390 | this.q.add("after", this.task2.bind(this), { run: false }); 391 | 392 | this.q.run(); 393 | this.q.on("paused", function () { 394 | self.runOrder.push("paused"); 395 | self.q.run(); 396 | }); 397 | this.q.once( 398 | "end", 399 | function () { 400 | assert.equal(this.runOrder[0], "task1"); 401 | assert.equal(this.runOrder[1], "pause"); 402 | assert.equal(this.runOrder[2], "paused"); 403 | assert.equal(this.runOrder[3], "task2"); 404 | assert.equal(this.runOrder[4], "pause"); 405 | assert.equal(this.runOrder[5], "paused"); 406 | assert.equal(this.runOrder[6], "task2"); 407 | assert.equal(this.runOrder[7], undefined); 408 | done(); 409 | }.bind(this), 410 | ); 411 | }); 412 | 413 | it("emit `error` event if the callback is called.", function (done) { 414 | this.q.on("error", (error) => { 415 | assert.equal(error, this.callbackError); 416 | done(); 417 | }); 418 | this.q.add("after", this.taskCallError); 419 | }); 420 | 421 | it("emit `error` event if the task throws.", function (done) { 422 | this.q.on("error", (error) => { 423 | assert.equal(error, this.throwError); 424 | done(); 425 | }); 426 | this.q.add("after", this.taskThrowError); 427 | }); 428 | 429 | it("emit `error` event if the task rejects.", function (done) { 430 | this.q.on("error", (error) => { 431 | assert.equal(error, this.rejectError); 432 | done(); 433 | }); 434 | this.q.add("after", this.taskRejectError); 435 | }); 436 | }); 437 | }); 438 | --------------------------------------------------------------------------------