├── .gitignore
├── .npmignore
├── example
├── index.html
└── index.js
├── index.d.ts
├── index.js
├── license.md
├── package.json
├── readme.md
└── test
├── array-methods.js
├── autostart.js
├── concurrent.js
├── end.js
├── error-async.js
├── error-promise.js
├── error-sync.js
├── index.html
├── index.js
├── length.js
├── promises.js
├── results.js
├── resume.js
├── start.js
├── stop-sync.js
├── stop.js
├── synchronous.js
└── timeout.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .idea
3 | node_modules
4 | coverage
5 | .vscode
6 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | test
2 | example
3 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
8 |
9 |
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 | import Queue from 'queue'
2 |
3 | const q = new Queue({ results: [] })
4 |
5 | // add jobs using the familiar Array API
6 | q.push(cb => {
7 | const result = 'two'
8 | cb(null, result)
9 | })
10 |
11 | q.push(
12 | cb => {
13 | const result = 'four'
14 | cb(null, result)
15 | },
16 | cb => {
17 | const result = 'five'
18 | cb(null, result)
19 | }
20 | )
21 |
22 | // jobs can accept a callback or return a promise
23 | q.push(() => {
24 | return new Promise((resolve, reject) => {
25 | const result = 'one'
26 | resolve(result)
27 | })
28 | })
29 |
30 | q.unshift(cb => {
31 | const result = 'one'
32 | cb(null, result)
33 | })
34 |
35 | q.splice(2, 0, cb => {
36 | const result = 'three'
37 | cb(null, result)
38 | })
39 |
40 | // use the timeout feature to deal with jobs that
41 | // take too long or forget to execute a callback
42 | q.timeout = 100
43 |
44 | q.addEventListener('timeout', e => {
45 | console.log('job timed out:', e.detail.job.toString().replace(/\n/g, ''))
46 | e.detail.next()
47 | })
48 |
49 | q.push(cb => {
50 | setTimeout(() => {
51 | console.log('slow job finished')
52 | cb()
53 | }, 200)
54 | })
55 |
56 | q.push(cb => {
57 | console.log('forgot to execute callback')
58 | })
59 |
60 | // jobs can also override the queue's timeout
61 | // on a per-job basis
62 | function extraSlowJob (cb) {
63 | setTimeout(() => {
64 | console.log('extra slow job finished')
65 | cb()
66 | }, 400)
67 | }
68 |
69 | extraSlowJob.timeout = 500
70 | q.push(extraSlowJob)
71 |
72 | // jobs can also opt-out of the timeout altogether
73 | function superSlowJob (cb) {
74 | setTimeout(() => {
75 | console.log('super slow job finished')
76 | cb()
77 | }, 1000)
78 | }
79 |
80 | superSlowJob.timeout = null
81 | q.push(superSlowJob)
82 |
83 | // get notified when jobs complete
84 | q.addEventListener('success', e => {
85 | console.log('job finished processing:', e.detail.toString().replace(/\n/g, ''))
86 | console.log('The result is:', e.detail.result)
87 | })
88 |
89 | // begin processing, get notified on end / failure
90 | q.start(err => {
91 | if (err) throw err
92 | console.log('all done:', q.results)
93 | })
94 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for Queue
2 | // Project: https://github.com/jessetane/queue
3 | // Definitions by: Alex Miller
4 | // Additions by Maksim Lavrenyuk
5 |
6 |
7 |
8 | type EventsMap = {
9 | end: { error?: Error }
10 | error: { error: Error, job: QueueWorker }
11 | timeout: { next: (err?: Error, ...result: any[]) => void, job: QueueWorker }
12 | success: { result: any[] }
13 | start: { job?: QueueWorker }
14 | }
15 |
16 | export class QueueEvent extends Event {
17 | readonly detail: Detail
18 | constructor(name: Name, detail: Detail)
19 | }
20 |
21 |
22 | type EventListenerOrEventListenerObject> = (event: Event) => void | {
23 | handleEvent(Event: Event): void;
24 | };
25 |
26 |
27 | export interface Options {
28 | /**
29 | * Max number of jobs the queue should process concurrently.
30 | *
31 | * @default Infinity
32 | */
33 | concurrency?: number;
34 |
35 | /**
36 | * Milliseconds to wait for a job to execute its callback.
37 | *
38 | * @default 0
39 | */
40 | timeout?: number;
41 |
42 | /**
43 | * Ensures the queue is always running if jobs are available. Useful in situations where you are using a queue only for concurrency control.
44 | *
45 | * @default false
46 | */
47 | autostart?: boolean;
48 |
49 | /**
50 | * An array to set job callback arguments on.
51 | *
52 | * @default null
53 | */
54 | results?: any[];
55 | }
56 |
57 | export default class Queue extends EventTarget {
58 | constructor(options?: Options)
59 |
60 | /**
61 | * Max number of jobs the queue should process concurrently.
62 | */
63 | concurrency: number;
64 |
65 | /**
66 | * Milliseconds to wait for a job to execute its callback.
67 | */
68 | timeout: number;
69 |
70 | /**
71 | * Ensures the queue is always running if jobs are available.
72 | */
73 | autostart: boolean;
74 |
75 | /**
76 | * An array to set job callback arguments on.
77 | */
78 | results: any[] | null;
79 |
80 | /**
81 | * Jobs pending + jobs to process.
82 | */
83 | readonly length: number;
84 |
85 | /**
86 | * Adds one or more elements to the end of the Queue and returns the new length of the Queue.
87 | *
88 | * @param workers New workers of the Queue.
89 | */
90 | push(...workers: QueueWorker[]): number;
91 |
92 | /**
93 | * Adds one or more elements to the front of the Queue and returns the new length of the Queue.
94 | *
95 | * @param workers Workers to insert at the start of the Queue.
96 | */
97 | unshift(...workers: QueueWorker[]): number;
98 |
99 | /**
100 | * Adds and/or removes elements from the queue.
101 | *
102 | * @param start The zero-based location in the Queue from which to start removing elements.
103 | * @param deleteCount The number of elements to remove.
104 | */
105 | splice(start: number, deleteCount?: number): Queue;
106 |
107 | /**
108 | * Adds and/or removes elements from the queue.
109 | *
110 | * @param start The zero-based location in the Queue from which to start removing elements.
111 | * @param deleteCount The number of elements to remove.
112 | * @param workers Workers to insert into the Queue in place of the deleted elements.
113 | */
114 | splice(start: number, deleteCount: number, ...workers: QueueWorker[]): Queue;
115 |
116 | /**
117 | * Removes the last element from the Queue and returns that element.
118 | */
119 | pop(): QueueWorker | undefined;
120 |
121 | /**
122 | * Removes the first element from the Queue and returns that element.
123 | */
124 | shift(): QueueWorker | undefined;
125 |
126 | /**
127 | * Extracts a section of the Queue and returns Queue.
128 | *
129 | * @param start The beginning of the specified portion of the Queue.
130 | * @param end The end of the specified portion of the Queue.
131 | */
132 | slice(start?: number, end?: number): Queue;
133 |
134 | /**
135 | * Reverses the order of the elements of the Queue in place.
136 | */
137 | reverse(): Queue;
138 |
139 | /**
140 | * Returns the first (least) index of an element within the Queue equal to the specified value, or -1 if none is found.
141 | *
142 | * @param searchElement The value to locate in the Queue.
143 | * @param fromIndex The Queue index at which to begin the search. If omitted, the search starts at index 0.
144 | */
145 | indexOf(searchElement: QueueWorker, fromIndex?: number): number;
146 |
147 | /**
148 | * Returns the last (greatest) index of an element within the Queue equal to the specified value, or -1 if none is found.
149 | *
150 | * @param searchElement The value to locate in the Queue.
151 | * @param fromIndex The Queue index at which to begin the search. If omitted, the search starts at the last index in the Queue.
152 | */
153 | lastIndexOf(searchElement: QueueWorker, fromIndex?: number): number;
154 |
155 | /**
156 | * Starts the queue.
157 | *
158 | * @param callback Callback to be called when the queue empties or when an error occurs.
159 | */
160 | start(callback: (error?: Error, results?: any[] | null) => void): void;
161 |
162 | start(): Promise<{ error?: Error, results?: any[] | null }>;
163 |
164 | start(): void;
165 |
166 | /**
167 | * Stops the queue.
168 | */
169 | stop(): void;
170 |
171 | /**
172 | * Stop and empty the queue immediately.
173 | *
174 | * @param error error of why the stop has occurred, to be passed to start callback if supplied.
175 | */
176 | end(error?: Error): void;
177 |
178 | addEventListener(name: Event, callback: EventListenerOrEventListenerObject>, options?: AddEventListenerOptions | boolean): void;
179 |
180 | dispatchEvent(event: QueueEvent): boolean;
181 |
182 | removeEventListener(name: Event, callback: EventListenerOrEventListenerObject>, options?: EventListenerOptions | boolean): void;
183 | }
184 |
185 | export interface QueueWorker {
186 | (callback?: QueueWorkerCallback): void | Promise;
187 |
188 | /**
189 | * Override queue timeout.
190 | */
191 | timeout?: number;
192 | /**
193 | * If the QueueWorker returns a promise, it will be moved to this field.
194 | * This can be useful when tracking timeout events
195 | */
196 | promise?: Promise
197 | }
198 |
199 | export interface QueueWorkerCallback {
200 | (error?: Error, data?: Object): void;
201 | }
202 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const has = Object.prototype.hasOwnProperty
2 |
3 | /**
4 | * Since CustomEvent is only supported in nodejs since version 19,
5 | * you have to create your own class instead of using CustomEvent
6 | * @see https://github.com/nodejs/node/issues/40678
7 | * */
8 | export class QueueEvent extends Event {
9 | constructor (name, detail) {
10 | super(name)
11 | this.detail = detail
12 | }
13 | }
14 |
15 | export default class Queue extends EventTarget {
16 | constructor (options = {}) {
17 | super()
18 | const { concurrency = Infinity, timeout = 0, autostart = false, results = null } = options
19 | this.concurrency = concurrency
20 | this.timeout = timeout
21 | this.autostart = autostart
22 | this.results = results
23 | this.pending = 0
24 | this.session = 0
25 | this.running = false
26 | this.jobs = []
27 | this.timers = []
28 | this.addEventListener('error', this._errorHandler)
29 | }
30 |
31 | _errorHandler (evt) {
32 | this.end(evt.detail.error)
33 | }
34 |
35 | pop () {
36 | return this.jobs.pop()
37 | }
38 |
39 | shift () {
40 | return this.jobs.shift()
41 | }
42 |
43 | indexOf (searchElement, fromIndex) {
44 | return this.jobs.indexOf(searchElement, fromIndex)
45 | }
46 |
47 | lastIndexOf (searchElement, fromIndex) {
48 | if (fromIndex !== undefined) return this.jobs.lastIndexOf(searchElement, fromIndex)
49 | return this.jobs.lastIndexOf(searchElement)
50 | }
51 |
52 | slice (start, end) {
53 | this.jobs = this.jobs.slice(start, end)
54 | return this
55 | }
56 |
57 | reverse () {
58 | this.jobs.reverse()
59 | return this
60 | }
61 |
62 | push (...workers) {
63 | const methodResult = this.jobs.push(...workers)
64 | if (this.autostart) this._start()
65 | return methodResult
66 | }
67 |
68 | unshift (...workers) {
69 | const methodResult = this.jobs.unshift(...workers)
70 | if (this.autostart) this._start()
71 | return methodResult
72 | }
73 |
74 | splice (start, deleteCount, ...workers) {
75 | this.jobs.splice(start, deleteCount, ...workers)
76 | if (this.autostart) this._start()
77 | return this
78 | }
79 |
80 | get length () {
81 | return this.pending + this.jobs.length
82 | }
83 |
84 | start (callback) {
85 | if (this.running) throw new Error('already started')
86 | let awaiter
87 | if (callback) {
88 | this._addCallbackToEndEvent(callback)
89 | } else {
90 | awaiter = this._createPromiseToEndEvent()
91 | }
92 | this._start()
93 | return awaiter
94 | }
95 |
96 | _start () {
97 | this.running = true
98 | if (this.pending >= this.concurrency) {
99 | return
100 | }
101 | if (this.jobs.length === 0) {
102 | if (this.pending === 0) {
103 | this.done()
104 | }
105 | return
106 | }
107 | const job = this.jobs.shift()
108 | const session = this.session
109 | const timeout = (job !== undefined) && has.call(job, 'timeout') ? job.timeout : this.timeout
110 | let once = true
111 | let timeoutId = null
112 | let didTimeout = false
113 | let resultIndex = null
114 | const next = (error, ...result) => {
115 | if (once && this.session === session) {
116 | once = false
117 | this.pending--
118 | if (timeoutId !== null) {
119 | this.timers = this.timers.filter(tID => tID !== timeoutId)
120 | clearTimeout(timeoutId)
121 | }
122 | if (error) {
123 | this.dispatchEvent(new QueueEvent('error', { error, job }))
124 | } else if (!didTimeout) {
125 | if (resultIndex !== null && this.results !== null) {
126 | this.results[resultIndex] = [...result]
127 | }
128 | this.dispatchEvent(new QueueEvent('success', { result: [...result], job }))
129 | }
130 | if (this.session === session) {
131 | if (this.pending === 0 && this.jobs.length === 0) {
132 | this.done()
133 | } else if (this.running) {
134 | this._start()
135 | }
136 | }
137 | }
138 | }
139 | if (timeout) {
140 | timeoutId = setTimeout(() => {
141 | didTimeout = true
142 | this.dispatchEvent(new QueueEvent('timeout', { next, job }))
143 | next()
144 | }, timeout)
145 | this.timers.push(timeoutId)
146 | }
147 | if (this.results != null) {
148 | resultIndex = this.results.length
149 | this.results[resultIndex] = null
150 | }
151 | this.pending++
152 | this.dispatchEvent(new QueueEvent('start', { job }))
153 | job.promise = job(next)
154 | if (job.promise !== undefined && typeof job.promise.then === 'function') {
155 | job.promise.then(function (result) {
156 | return next(undefined, result)
157 | }).catch(function (err) {
158 | return next(err || true)
159 | })
160 | }
161 | if (this.running && this.jobs.length > 0) {
162 | this._start()
163 | }
164 | }
165 |
166 | stop () {
167 | this.running = false
168 | }
169 |
170 | end (error) {
171 | this.clearTimers()
172 | this.jobs.length = 0
173 | this.pending = 0
174 | this.done(error)
175 | }
176 |
177 | clearTimers () {
178 | this.timers.forEach(timer => {
179 | clearTimeout(timer)
180 | })
181 | this.timers = []
182 | }
183 |
184 | _addCallbackToEndEvent (cb) {
185 | const onend = evt => {
186 | this.removeEventListener('end', onend)
187 | cb(evt.detail.error, this.results)
188 | }
189 | this.addEventListener('end', onend)
190 | }
191 |
192 | _createPromiseToEndEvent () {
193 | return new Promise((resolve, reject) => {
194 | this._addCallbackToEndEvent((error, results) => {
195 | if (error) reject(error)
196 | else resolve(results)
197 | })
198 | })
199 | }
200 |
201 | done (error) {
202 | this.session++
203 | this.running = false
204 | this.dispatchEvent(new QueueEvent('end', { error }))
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2012-23 [these people](https://github.com/jessetane/queue/graphs/contributors)
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "queue",
3 | "version": "7.0.0",
4 | "description": "asynchronous function queue with adjustable concurrency",
5 | "keywords": [
6 | "queue",
7 | "async",
8 | "asynchronous",
9 | "synchronous",
10 | "job",
11 | "task",
12 | "concurrency",
13 | "concurrent"
14 | ],
15 | "files": [
16 | "index.js",
17 | "index.d.ts"
18 | ],
19 | "typings": "index.d.ts",
20 | "main": "index.js",
21 | "exports": {
22 | ".": "./index.js",
23 | "./*": "./*"
24 | },
25 | "type": "module",
26 | "devDependencies": {
27 | "c8": "^7.12.0",
28 | "http-server": "^14.1.1",
29 | "tap-esm": "^3.0.0"
30 | },
31 | "scripts": {
32 | "test": "node test",
33 | "coverage": "c8 npm run test",
34 | "dev": "http-server ./ -s -c-1 -p 7357",
35 | "example": "node example"
36 | },
37 | "repository": "https://github.com/jessetane/queue.git",
38 | "contributors": [
39 | "Jesse Tane ",
40 | "Maksim Lavrenyuk "
41 | ],
42 | "license": "MIT"
43 | }
44 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | ```
2 | ____ __ _____ __ _____
3 | / __ `/ / / / _ \/ / / / _ \
4 | / /_/ / /_/ / __/ /_/ / __/
5 | \__, /\__,_/\___/\__,_/\___/
6 | /_/
7 | ```
8 | Asynchronous function queue with adjustable concurrency.
9 |
10 | [](http://www.npmjs.org/queue)
11 |
12 | This module exports a class `Queue` that implements most of the `Array` API. Pass async functions (ones that accept a callback or return a promise) to an instance's additive array methods. Processing begins when you call `q.start()`.
13 |
14 | ## Example
15 | Do `npm run example` or `npm run dev` and open the example directory (and your console) to run the following program:
16 | ``` javascript
17 | import Queue from 'queue'
18 |
19 | const q = new Queue({ results: [] })
20 |
21 | // add jobs using the familiar Array API
22 | q.push(cb => {
23 | const result = 'two'
24 | cb(null, result)
25 | })
26 |
27 | q.push(
28 | cb => {
29 | const result = 'four'
30 | cb(null, result)
31 | },
32 | cb => {
33 | const result = 'five'
34 | cb(null, result)
35 | }
36 | )
37 |
38 | // jobs can accept a callback or return a promise
39 | q.push(() => {
40 | return new Promise((resolve, reject) => {
41 | const result = 'one'
42 | resolve(result)
43 | })
44 | })
45 |
46 | q.unshift(cb => {
47 | const result = 'one'
48 | cb(null, result)
49 | })
50 |
51 | q.splice(2, 0, cb => {
52 | const result = 'three'
53 | cb(null, result)
54 | })
55 |
56 | // use the timeout feature to deal with jobs that
57 | // take too long or forget to execute a callback
58 | q.timeout = 100
59 |
60 | q.addEventListener('timeout', e => {
61 | console.log('job timed out:', e.detail.job.toString().replace(/\n/g, ''))
62 | e.detail.next()
63 | })
64 |
65 | q.push(cb => {
66 | setTimeout(() => {
67 | console.log('slow job finished')
68 | cb()
69 | }, 200)
70 | })
71 |
72 | q.push(cb => {
73 | console.log('forgot to execute callback')
74 | })
75 |
76 | // jobs can also override the queue's timeout
77 | // on a per-job basis
78 | function extraSlowJob (cb) {
79 | setTimeout(() => {
80 | console.log('extra slow job finished')
81 | cb()
82 | }, 400)
83 | }
84 |
85 | extraSlowJob.timeout = 500
86 | q.push(extraSlowJob)
87 |
88 | // jobs can also opt-out of the timeout altogether
89 | function superSlowJob (cb) {
90 | setTimeout(() => {
91 | console.log('super slow job finished')
92 | cb()
93 | }, 1000)
94 | }
95 |
96 | superSlowJob.timeout = null
97 | q.push(superSlowJob)
98 |
99 | // get notified when jobs complete
100 | q.addEventListener('success', e => {
101 | console.log('job finished processing:', e.detail.toString().replace(/\n/g, ''))
102 | console.log('The result is:', e.detail.result)
103 | })
104 |
105 | // begin processing, get notified on end / failure
106 | q.start(err => {
107 | if (err) throw err
108 | console.log('all done:', q.results)
109 | })
110 | ```
111 |
112 | ## Install
113 |
114 | ```
115 | npm install queue
116 |
117 | yarn add queue
118 | ```
119 |
120 | ## Test
121 |
122 | ```
123 | npm test
124 |
125 | npm run dev // for testing in a browser, open test directory (and your console)
126 | ```
127 |
128 | ## API
129 |
130 | ### `const q = new Queue([opts])`
131 | Constructor. `opts` may contain initial values for:
132 | * `q.concurrency`
133 | * `q.timeout`
134 | * `q.autostart`
135 | * `q.results`
136 |
137 | ## Instance methods
138 | ### `q.start([cb])`
139 | Explicitly starts processing jobs and provides feedback to the caller when the queue empties or an error occurs. If cb is not passed a promise will be returned.
140 |
141 | ### `q.stop()`
142 | Stops the queue. can be resumed with `q.start()`.
143 |
144 | ### `q.end([err])`
145 | Stop and empty the queue immediately.
146 |
147 | ## Instance methods mixed in from `Array`
148 | Mozilla has docs on how these methods work [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array). Note that `slice` does not copy the queue.
149 | ### `q.push(element1, ..., elementN)`
150 | ### `q.unshift(element1, ..., elementN)`
151 | ### `q.splice(index , howMany[, element1[, ...[, elementN]]])`
152 | ### `q.pop()`
153 | ### `q.shift()`
154 | ### `q.slice(begin[, end])`
155 | ### `q.reverse()`
156 | ### `q.indexOf(searchElement[, fromIndex])`
157 | ### `q.lastIndexOf(searchElement[, fromIndex])`
158 |
159 | ## Properties
160 | ### `q.concurrency`
161 | Max number of jobs the queue should process concurrently, defaults to `Infinity`.
162 |
163 | ### `q.timeout`
164 | Milliseconds to wait for a job to execute its callback. This can be overridden by specifying a `timeout` property on a per-job basis.
165 |
166 | ### `q.autostart`
167 | Ensures the queue is always running if jobs are available. Useful in situations where you are using a queue only for concurrency control.
168 |
169 | ### `q.results`
170 | An array to set job callback arguments on.
171 |
172 | ### `q.length`
173 | Jobs pending + jobs to process (readonly).
174 |
175 | ## Events
176 |
177 | ### `q.dispatchEvent(new QueueEvent('start', { job }))`
178 | Immediately before a job begins to execute.
179 |
180 | ### `q.dispatchEvent(new QueueEvent('success', { result: [...result], job }))`
181 | After a job executes its callback.
182 |
183 | ### `q.dispatchEvent(new QueueEvent('error', { err, job }))`
184 | After a job passes an error to its callback.
185 |
186 | ### `q.dispatchEvent(new QueueEvent('timeout', { next, job }))`
187 | After `q.timeout` milliseconds have elapsed and a job has not executed its callback.
188 |
189 | ### `q.dispatchEvent(new QueueEvent('end', { err }))`
190 | After all jobs have been processed
191 |
192 | ## Releases
193 | The latest stable release is published to [npm](http://npmjs.org/queue). Abbreviated changelog below:
194 | * [7.0](https://github.com/jessetane/queue/archive/7.0.0.tar.gz)
195 | * Modernized codebase, added new maintainer (@MaksimLavrenyuk)
196 | * [6.0](https://github.com/jessetane/queue/archive/6.0.1.tar.gz)
197 | * Add `start` event before job begins (@joelgriffith)
198 | * Add `timeout` property on a job to override the queue's timeout (@joelgriffith)
199 | * [5.0](https://github.com/jessetane/queue/archive/5.0.0.tar.gz)
200 | * Updated TypeScript bindings (@Codex-)
201 | * [4.4](https://github.com/jessetane/queue/archive/4.4.0.tar.gz)
202 | * Add results feature
203 | * [4.3](https://github.com/jessetane/queue/archive/4.3.0.tar.gz)
204 | * Add promise support (@kwolfy)
205 | * [4.2](https://github.com/jessetane/queue/archive/4.2.0.tar.gz)
206 | * Unref timers on end
207 | * [4.1](https://github.com/jessetane/queue/archive/4.1.0.tar.gz)
208 | * Add autostart feature
209 | * [4.0](https://github.com/jessetane/queue/archive/4.0.0.tar.gz)
210 | * Change license to MIT
211 | * [3.1.x](https://github.com/jessetane/queue/archive/3.0.6.tar.gz)
212 | * Add .npmignore
213 | * [3.0.x](https://github.com/jessetane/queue/archive/3.0.6.tar.gz)
214 | * Change the default concurrency to `Infinity`
215 | * Allow `q.start()` to accept an optional callback executed on `q.emit('end')`
216 | * [2.x](https://github.com/jessetane/queue/archive/2.2.0.tar.gz)
217 | * Major api changes / not backwards compatible with 1.x
218 | * [1.x](https://github.com/jessetane/queue/archive/1.0.2.tar.gz)
219 | * Early prototype
220 |
221 | ## License
222 | [MIT](https://github.com/jessetane/queue/blob/master/license.md)
223 |
--------------------------------------------------------------------------------
/test/array-methods.js:
--------------------------------------------------------------------------------
1 | import tap from 'tap-esm';
2 | import Queue from '../index.js'
3 |
4 | tap('pop sync', (t) => {
5 | t.plan(2)
6 |
7 | const q = new Queue()
8 | const results = []
9 |
10 | q.push((cb) => {
11 | results.push(1)
12 | cb()
13 | })
14 |
15 | q.push((cb) => {
16 | results.push(2)
17 | cb()
18 | })
19 |
20 | q.pop()
21 |
22 | q.start((err) => {
23 | t.notOk(err)
24 | t.arrayEqual(results, [ 1 ])
25 | })
26 | })
27 |
28 | tap('pop async', (t) => {
29 | t.plan(2)
30 | const q = new Queue({ concurrency: 1 })
31 | const results = []
32 |
33 | q.push((cb) => {
34 | results.push(1)
35 | setTimeout(cb, 10)
36 | })
37 |
38 | q.push((cb) => {
39 | results.push(2)
40 | cb()
41 | })
42 |
43 | q.start((err) => {
44 | t.notOk(err)
45 | t.arrayEqual(results, [ 1 ])
46 | })
47 |
48 | q.pop()
49 | })
50 |
51 | tap('shift sync', (t) => {
52 | t.plan(2)
53 |
54 | const q = new Queue()
55 | const results = []
56 |
57 | q.push((cb) => {
58 | results.push(1)
59 | cb()
60 | })
61 |
62 | q.push((cb) => {
63 | results.push(2)
64 | cb()
65 | })
66 |
67 | q.shift()
68 |
69 | q.start((err) => {
70 | t.notOk(err)
71 | t.arrayEqual(results, [ 2 ])
72 | })
73 | })
74 |
75 | tap('unshift with autostart', (t) => {
76 | t.plan(1)
77 | const q = new Queue({ autostart: true })
78 | let wasRunning = false
79 |
80 | q.unshift((cb) => {
81 | wasRunning = true
82 | cb()
83 | })
84 |
85 | t.ok(wasRunning)
86 | })
87 |
88 | tap('shift async', (t) => {
89 | t.plan(2)
90 | const q = new Queue({ concurrency: 1 })
91 | const results = []
92 |
93 | q.push((cb) => {
94 | results.push(1)
95 | setTimeout(cb, 10)
96 | })
97 |
98 | q.push((cb) => {
99 | results.push(2)
100 | cb()
101 | })
102 |
103 | q.start((err) => {
104 | t.notOk(err)
105 | t.arrayEqual(results, [ 1 ])
106 | })
107 |
108 | q.shift()
109 | })
110 |
111 | tap('slice sync', (t) => {
112 | t.plan(3)
113 |
114 | const q = new Queue()
115 | const results = []
116 |
117 | q.push((cb) => {
118 | results.push(1)
119 | cb()
120 | })
121 |
122 | q.push((cb) => {
123 | results.push(2)
124 | cb()
125 | })
126 |
127 | t.equal(q, q.slice(0, 1))
128 |
129 | q.start((err) => {
130 | t.notOk(err)
131 | t.arrayEqual(results, [ 1 ])
132 | })
133 | })
134 |
135 | tap('slice async', (t) => {
136 | t.plan(3)
137 | const q = new Queue({ concurrency: 1 })
138 | const results = []
139 |
140 | q.push((cb) => {
141 | results.push(1)
142 | setTimeout(cb, 10)
143 | })
144 |
145 | q.push((cb) => {
146 | results.push(2)
147 | cb()
148 | })
149 |
150 | q.start((err) => {
151 | t.notOk(err)
152 | t.arrayEqual(results, [ 1 ])
153 | })
154 |
155 | t.equal(q, q.slice(0, 0))
156 | })
157 |
158 | tap('splice with autostart', (t) => {
159 | t.plan(2)
160 |
161 | const q = new Queue({ autostart: true, concurrency: 1 })
162 | const results = []
163 |
164 | q.push((cb) => {
165 | results.push(1)
166 | setTimeout(cb, 5)
167 | })
168 |
169 | q.push((cb) => {
170 | results.push(2)
171 | cb()
172 | })
173 |
174 | t.equal(q, q.splice(0, 1))
175 |
176 | setTimeout(() => {
177 | t.arrayEqual(results, [1])
178 | }, 10)
179 | })
180 |
181 | tap('reverse sync', (t) => {
182 | t.plan(3)
183 |
184 | const q = new Queue()
185 | const results = []
186 |
187 | q.push((cb) => {
188 | results.push(1)
189 | cb()
190 | })
191 |
192 | q.push((cb) => {
193 | results.push(2)
194 | cb()
195 | })
196 |
197 | t.equal(q, q.reverse())
198 |
199 | q.start((err) => {
200 | t.notOk(err)
201 | t.arrayEqual(results, [ 2, 1 ])
202 | })
203 | })
204 |
205 | tap('indexOf', (t) => {
206 | t.plan(3)
207 |
208 | const q = new Queue()
209 | const results = []
210 | const job = (cb) => {
211 | results.push(results.length + 1)
212 | cb()
213 | }
214 |
215 | q.push(job)
216 | q.push(job)
217 |
218 | t.equal(q.indexOf(job), 0)
219 |
220 | q.start((err) => {
221 | t.notOk(err)
222 | t.arrayEqual(results, [ 1, 2 ])
223 | })
224 | })
225 |
226 | tap('lastIndexOf', (t) => {
227 | t.plan(4)
228 |
229 | const q = new Queue()
230 | const results = []
231 | const job = (cb) => {
232 | results.push(results.length + 1)
233 | cb()
234 | }
235 |
236 | q.push(job)
237 | q.push(job)
238 |
239 | t.equal(q.lastIndexOf(job), 1)
240 | t.equal(q.lastIndexOf(job, 1), 1)
241 |
242 | q.start((err) => {
243 | t.notOk(err)
244 | t.arrayEqual(results, [ 1, 2 ])
245 | })
246 | })
--------------------------------------------------------------------------------
/test/autostart.js:
--------------------------------------------------------------------------------
1 | import tap from 'tap-esm';
2 | import Queue from '../index.js'
3 |
4 | tap('autostart', (t) => {
5 | t.plan(9)
6 | const expected = ['one', 'two', 'three']
7 | const actual = []
8 | const q = new Queue({ autostart: true })
9 | let numEndHandlers = 0
10 |
11 | q.addEventListener('end', () => {
12 | numEndHandlers++
13 | t.equal(actual.length, numEndHandlers)
14 |
15 | actual.forEach((a, i) => {
16 | t.equal(actual[i], expected[i])
17 | })
18 | })
19 |
20 | q.push((cb) => {
21 | actual.push('one')
22 | cb()
23 | })
24 |
25 | q.push((cb) => {
26 | actual.push('two')
27 | cb()
28 | })
29 |
30 | setTimeout(() => {
31 | q.push((cb) => {
32 | actual.push('three')
33 | cb()
34 | })
35 | }, 10)
36 | })
37 |
--------------------------------------------------------------------------------
/test/concurrent.js:
--------------------------------------------------------------------------------
1 | import tap from 'tap-esm';
2 | import Queue from '../index.js'
3 |
4 | tap('concurrent', (t) => {
5 | t.plan(6)
6 | const actual = []
7 | const q = new Queue()
8 | q.concurrency = 2
9 |
10 | q.push((cb) => {
11 | setTimeout(() => {
12 | actual.push('two')
13 | cb()
14 | }, 20)
15 | })
16 |
17 | q.push((cb) => {
18 | setTimeout(() => {
19 | actual.push('one')
20 | cb()
21 | }, 0)
22 | })
23 |
24 | q.push((cb) => {
25 | q.concurrency = 1
26 | setTimeout(() => {
27 | actual.push('three')
28 | cb()
29 | }, 30)
30 | })
31 |
32 | q.push((cb) => {
33 | setTimeout(() => {
34 | actual.push('four')
35 | cb()
36 | }, 10)
37 | })
38 |
39 | q.push((cb) => {
40 | setTimeout(() => {
41 | actual.push('five')
42 | cb()
43 | }, 0)
44 | })
45 |
46 | q.start(() => {
47 | const expected = ['one', 'two', 'three', 'four', 'five']
48 | t.equal(actual.length, expected.length)
49 |
50 | actual.forEach((a, i) => {
51 | const e = expected[i]
52 | t.equal(a, e)
53 | })
54 | })
55 | })
--------------------------------------------------------------------------------
/test/end.js:
--------------------------------------------------------------------------------
1 | import tap from 'tap-esm';
2 | import Queue from '../index.js'
3 |
4 | tap('end', (t) => {
5 | t.plan(3)
6 | const q = new Queue()
7 |
8 | q.push((cb) => {
9 | setTimeout(cb, 0)
10 | })
11 |
12 | q.push((cb) => {
13 | setTimeout(() => {
14 | t.equal(q.length, 2)
15 | q.end(new Error('fake error'))
16 | setTimeout(() => {
17 | // session has changed so this should be a nop
18 | cb()
19 |
20 | // and we should still have one job left
21 | t.equal(q.length, 1)
22 | }, 10)
23 | }, 10)
24 | })
25 |
26 | q.push((cb) => {
27 | setTimeout(cb, 30)
28 | })
29 |
30 | q.start((err) => {
31 | t.equal(q.length, 0)
32 |
33 | if (err !== undefined) {
34 | q.push(() => {})
35 | }
36 | })
37 | })
--------------------------------------------------------------------------------
/test/error-async.js:
--------------------------------------------------------------------------------
1 | import tap from 'tap-esm';
2 | import Queue from '../index.js'
3 |
4 | tap('error-async', (t) => {
5 | t.plan(2)
6 | const q = new Queue({ autostart: true })
7 |
8 | q.addEventListener('end', (event) => {
9 | t.equal(event.detail.error.message, 'something broke') // 3
10 | t.equal(q.length, 0)
11 | })
12 |
13 | q.push((cb) => {
14 | setTimeout(cb, 10)
15 | })
16 |
17 | q.push((cb) => {
18 | setTimeout(() => {
19 | cb(new Error('something broke'))
20 | }, 20)
21 | })
22 |
23 | q.push((cb) => {
24 | setTimeout(cb, 30)
25 | })
26 | })
27 |
28 |
29 | tap('error-async. start() should return a promise with error data.', async (t) => {
30 | t.plan(2)
31 | const q = new Queue()
32 |
33 | q.push((cb) => {
34 | setTimeout(cb, 10)
35 | })
36 |
37 | q.push((cb) => {
38 | setTimeout(() => {
39 | cb(new Error('something broke'))
40 | }, 20)
41 | })
42 |
43 | q.push((cb) => {
44 | setTimeout(cb, 30)
45 | })
46 |
47 | try {
48 | await q.start()
49 | } catch (err) {
50 | t.equal(err.message, 'something broke') // 3
51 | t.equal(q.length, 0)
52 | }
53 | })
54 |
--------------------------------------------------------------------------------
/test/error-promise.js:
--------------------------------------------------------------------------------
1 | import tap from 'tap-esm';
2 | import Queue from '../index.js'
3 |
4 | tap('error-promise with error', (t) => {
5 | t.plan(2)
6 | const q = new Queue({ autostart: true })
7 |
8 | q.addEventListener('end', (event) => {
9 | t.equal(event.detail.error.message, 'something broke')
10 | t.equal(q.length, 0)
11 | })
12 |
13 | q.push((cb) => {
14 | setTimeout(cb, 10)
15 | })
16 |
17 | q.push(async () => {
18 | return await new Promise((resolve, reject) => {
19 | setTimeout(() => {
20 | reject(new Error('something broke'))
21 | }, 20)
22 | })
23 | })
24 |
25 | q.push(async () => {
26 | return await new Promise((resolve) => {
27 | setTimeout(() => {
28 | resolve()
29 | }, 30)
30 | })
31 | })
32 | })
33 |
34 | tap('error-promise with empty error', (t) => {
35 | t.plan(2)
36 | const q = new Queue({ autostart: true })
37 |
38 | q.addEventListener('end', (event) => {
39 | t.equal(event.detail.error, true)
40 | t.equal(q.length, 0)
41 | })
42 |
43 | q.push((cb) => {
44 | setTimeout(cb, 10)
45 | })
46 |
47 | q.push(async () => {
48 | return await new Promise((resolve, reject) => {
49 | setTimeout(() => {
50 | reject()
51 | }, 20)
52 | })
53 | })
54 |
55 | q.push(async () => {
56 | return await new Promise(function (resolve) {
57 | setTimeout(() => {
58 | resolve()
59 | }, 30)
60 | })
61 | })
62 | })
63 |
64 | tap('error-promise with error. start() should return a promise with error data.', async (t) => {
65 | t.plan(2)
66 | const q = new Queue()
67 |
68 | q.push((cb) => {
69 | setTimeout(cb, 10)
70 | })
71 |
72 | q.push(async () => {
73 | return await new Promise((resolve, reject) => {
74 | setTimeout(() => {
75 | reject(new Error('something broke'))
76 | }, 20)
77 | })
78 | })
79 |
80 | q.push(async () => {
81 | return await new Promise((resolve) => {
82 | setTimeout(() => {
83 | resolve()
84 | }, 30)
85 | })
86 | })
87 |
88 | try {
89 | await q.start()
90 | } catch (err) {
91 | t.equal(err.message, 'something broke')
92 | t.equal(q.length, 0)
93 | }
94 | })
95 |
96 |
97 |
98 | tap('error-promise with empty error. start() should return a promise with error data.', async (t) => {
99 | t.plan(2)
100 | const q = new Queue()
101 |
102 | q.push((cb) => {
103 | setTimeout(cb, 10)
104 | })
105 |
106 | q.push(async () => {
107 | return await new Promise((resolve, reject) => {
108 | setTimeout(() => {
109 | reject('something broke')
110 | }, 20)
111 | })
112 | })
113 |
114 | q.push(async () => {
115 | return await new Promise(function (resolve) {
116 | setTimeout(() => {
117 | resolve()
118 | }, 30)
119 | })
120 | })
121 |
122 | try {
123 | await q.start()
124 | } catch (err) {
125 | t.equal(err, 'something broke')
126 | t.equal(q.length, 0)
127 | }
128 | })
129 |
--------------------------------------------------------------------------------
/test/error-sync.js:
--------------------------------------------------------------------------------
1 | import tap from 'tap-esm';
2 | import Queue from '../index.js'
3 |
4 | tap('error-sync', (t) => {
5 | t.plan(2)
6 |
7 | const q = new Queue()
8 |
9 | q.push((cb) => {
10 | cb(new Error('something broke'))
11 | })
12 |
13 | q.start((err) => {
14 | t.equal(err.message, 'something broke')
15 | t.equal(q.length, 0)
16 | })
17 | })
18 |
19 |
20 | tap('error-sync. start() should return a promise with error data.', async (t) => {
21 | t.plan(2)
22 |
23 | const q = new Queue()
24 |
25 | q.push((cb) => {
26 | cb(new Error('something broke'))
27 | })
28 |
29 | try {
30 | await q.start()
31 | } catch (err) {
32 | t.equal(err.message, 'something broke')
33 | t.equal(q.length, 0)
34 | }
35 | })
36 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
8 |
9 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | import './synchronous.js'
2 | import './concurrent.js'
3 | import './timeout.js'
4 | import './length.js'
5 | import './start.js'
6 | import './stop.js'
7 | import './end.js'
8 | import './error-sync.js'
9 | import './error-async.js'
10 | import './error-promise.js'
11 | import './resume.js'
12 | import './autostart.js'
13 | import './array-methods.js'
14 | import './promises.js'
15 | import './results.js'
16 |
--------------------------------------------------------------------------------
/test/length.js:
--------------------------------------------------------------------------------
1 | import tap from 'tap-esm';
2 | import Queue from '../index.js'
3 |
4 | tap('length', (t) => {
5 | t.plan(12)
6 | const q = new Queue()
7 |
8 | q.push((cb) => {
9 | setTimeout(() => {
10 | t.equal(q.length, 3)
11 | cb()
12 | t.equal(q.length, 2)
13 | }, 0)
14 | })
15 |
16 | q.push((cb) => {
17 | setTimeout(() => {
18 | t.equal(q.length, 2)
19 | cb()
20 | t.equal(q.length, 1)
21 | }, 10)
22 | })
23 |
24 | q.push((cb) => {
25 | setTimeout(() => {
26 | t.equal(q.length, 1)
27 | cb()
28 | t.equal(q.length, 0)
29 | }, 20)
30 | })
31 |
32 | t.equal(q.pending, 0)
33 | t.equal(q.length, 3)
34 |
35 | q.start(() => {
36 | t.equal(q.pending, 0)
37 | t.equal(q.length, 0)
38 | })
39 |
40 | t.equal(q.pending, 3)
41 | t.equal(q.length, 3)
42 | })
--------------------------------------------------------------------------------
/test/promises.js:
--------------------------------------------------------------------------------
1 | import tap from 'tap-esm';
2 | import Queue from '../index.js'
3 |
4 | tap('promises', (t) => {
5 | t.plan(4)
6 | const actual = []
7 | const q = new Queue({ concurrency: 1 })
8 |
9 | q.addEventListener('end', () => {
10 | const expected = ['one', 'two', 'three']
11 | t.equal(actual.length, expected.length)
12 |
13 | actual.forEach((a, i) => {
14 | const e = expected[i]
15 | t.equal(a, e)
16 | })
17 | })
18 |
19 | q.push(async () => {
20 | actual.push('three')
21 | })
22 |
23 | q.unshift(async () => {
24 | actual.push('one')
25 | })
26 |
27 | q.splice(1, 0, async () => {
28 | actual.push('two')
29 | })
30 |
31 | q.start()
32 | })
33 |
34 | tap('promise returned from the job must be set to the job.promise property', (t) => {
35 | t.plan(1)
36 | const q = new Queue({ concurrency: 1 })
37 | let promise;
38 |
39 | q.addEventListener('success',(event) => {
40 | t.equal(event.detail.job.promise, promise)
41 | })
42 |
43 | q.push(() => {
44 | promise = new Promise((resolve) => resolve());
45 |
46 | return promise;
47 | })
48 |
49 | q.start()
50 | })
51 |
52 | tap('promise returned from an async job should be set to the job.promise property', (t) => {
53 | t.plan(1)
54 | const q = new Queue({ concurrency: 1 })
55 |
56 | q.addEventListener('success',async (event) => {
57 | const result = await event.detail.job.promise;
58 |
59 | t.equal(result, 'result');
60 | })
61 |
62 | q.push(async () => 'result');
63 |
64 | q.start()
65 | })
--------------------------------------------------------------------------------
/test/results.js:
--------------------------------------------------------------------------------
1 | import tap from 'tap-esm';
2 | import Queue from '../index.js'
3 |
4 | tap('results', (t) => {
5 | t.plan(5)
6 | const q = new Queue({ results: [] })
7 |
8 | q.push(
9 | (cb) => cb(undefined, 42),
10 | (cb) => cb(undefined, 3, 2, 1),
11 | (cb) => cb()
12 | )
13 |
14 | q.unshift((cb) => {
15 | setTimeout(() => {
16 | cb(undefined, 'string')
17 | }, 10)
18 | })
19 |
20 | q.start((err, results) => {
21 | t.notOk(err);
22 |
23 | [
24 | ['string'],
25 | [42],
26 | [3, 2, 1],
27 | []
28 | ].forEach((expected, i) => {
29 | t.arrayEqual(expected, results[i])
30 | })
31 | })
32 | })
33 |
34 | tap('results from start(). start() should return a promise with error data and results', async (t) => {
35 | t.plan(5)
36 | const q = new Queue({ results: [] })
37 |
38 | q.push(
39 | (cb) => cb(undefined, 42),
40 | (cb) => cb(undefined, 3, 2, 1),
41 | (cb) => cb()
42 | )
43 |
44 | q.unshift((cb) => {
45 | setTimeout(() => {
46 | cb(undefined, 'string')
47 | }, 10)
48 | })
49 |
50 | try {
51 | await q.start()
52 | t.pass()
53 | } catch (err) {
54 | t.notOk(err)
55 | }
56 |
57 | [
58 | ['string'],
59 | [42],
60 | [3, 2, 1],
61 | []
62 | ].forEach((expected, i) => {
63 | t.arrayEqual(expected, q.results[i])
64 | })
65 | });
66 |
--------------------------------------------------------------------------------
/test/resume.js:
--------------------------------------------------------------------------------
1 | import tap from 'tap-esm';
2 | import Queue from '../index.js'
3 |
4 | tap('resume', (t) => {
5 | t.plan(16)
6 | const q = new Queue({ concurrency: 2 })
7 |
8 | let jobsToSet = 16
9 |
10 | while (jobsToSet-- !== 0) {
11 | q.push((cb) => {
12 | setTimeout(() => {
13 | t.ok(q)
14 | cb()
15 | }, 10)
16 | })
17 | }
18 |
19 | // start
20 | q.start()
21 |
22 | // and stop somewhere in the middle of queue
23 | setTimeout(function () {
24 | q.stop()
25 | q.start()
26 | }, 30)
27 | })
--------------------------------------------------------------------------------
/test/start.js:
--------------------------------------------------------------------------------
1 | import tap from 'tap-esm';
2 | import Queue from '../index.js'
3 |
4 | tap('start', (t) => {
5 | t.plan(4)
6 |
7 | const q = new Queue()
8 | const work = (cb) => {
9 | t.ok(q)
10 | cb()
11 | }
12 |
13 | q.addEventListener('start', (event) => {
14 | t.equal(event.detail.job, work)
15 | })
16 |
17 | q.push(work)
18 |
19 | q.start(() => {
20 | t.ok(q)
21 |
22 | q.start(() => {
23 | t.ok(q)
24 | })
25 | })
26 | })
27 |
28 | tap('await start. Push the job, start() returns Promise waiting for all jobs to finish', async (t) => {
29 | t.plan(1)
30 | const queue = new Queue();
31 | const result = [];
32 | const work = (cb) => {
33 | setTimeout(() => {
34 | result.push(1);
35 | cb();
36 | }, 30)
37 | }
38 |
39 | queue.push(work);
40 | await queue.start();
41 | result.push(2);
42 | console.log(result);
43 |
44 | t.arrayEqual(result, [1, 2]);
45 | });
46 |
--------------------------------------------------------------------------------
/test/stop-sync.js:
--------------------------------------------------------------------------------
1 | import tap from 'tap-esm';
2 | import Queue from '../index.js'
3 |
4 | tap('stop-sync', (t) => {
5 | t.plan(2)
6 | const q = new Queue({ concurrency: 1 })
7 | let n = 0
8 |
9 | q.push((cb) => {
10 | n++
11 | q.stop()
12 | cb()
13 | })
14 |
15 | q.push((cb) => {
16 | n++
17 | cb()
18 | })
19 |
20 | q.start()
21 |
22 | setTimeout(() => {
23 | t.equal(q.length, 1)
24 | t.equal(n, 1)
25 | }, 0)
26 | })
--------------------------------------------------------------------------------
/test/stop.js:
--------------------------------------------------------------------------------
1 | import tap from 'tap-esm';
2 | import Queue from '../index.js'
3 |
4 | tap('stop', (t) => {
5 | t.plan(6)
6 | const q = new Queue({ concurrency: 1 })
7 |
8 | q.push((cb) => {
9 | setTimeout(() => {
10 | t.equal(q.running, false)
11 | if (cb !== undefined) cb()
12 |
13 | // restart
14 | setTimeout(() => {
15 | q.start(() => {
16 | t.ok(q)
17 | })
18 | }, 10)
19 | }, 10)
20 | })
21 |
22 | q.push((cb) => {
23 | t.equal(q.running, true)
24 | cb()
25 | })
26 |
27 | // start
28 | q.start((err) => {
29 | t.notOk(err)
30 | t.equal(q.running, false)
31 | })
32 |
33 | // but stop the q before the first job has finished
34 | setTimeout(() => {
35 | t.equal(q.length, 2)
36 | q.stop()
37 | }, 0)
38 | })
--------------------------------------------------------------------------------
/test/synchronous.js:
--------------------------------------------------------------------------------
1 | import tap from 'tap-esm';
2 | import Queue from '../index.js'
3 |
4 | tap('synchronous', (t) => {
5 | t.plan(4)
6 |
7 | const actual = []
8 | const q = new Queue({ concurrency: 1 })
9 |
10 | q.addEventListener('end', () => {
11 | const expected = ['one', 'two', 'three']
12 | t.equal(actual.length, expected.length)
13 |
14 | actual.forEach((a, i) => {
15 | const e = expected[i]
16 | t.equal(a, e)
17 | })
18 | })
19 |
20 | q.push((cb) => {
21 | actual.push('three')
22 | cb()
23 | })
24 |
25 | q.unshift((cb) => {
26 | actual.push('one')
27 | cb()
28 | })
29 |
30 | q.splice(1, 0, (cb) => {
31 | actual.push('two')
32 | cb()
33 | })
34 |
35 | q.start()
36 | })
37 |
--------------------------------------------------------------------------------
/test/timeout.js:
--------------------------------------------------------------------------------
1 | import tap from 'tap-esm';
2 | import Queue from '../index.js'
3 |
4 | tap('timeout', (t) => {
5 | t.plan(4)
6 | const actual = []
7 | const q = new Queue({ timeout: 10 })
8 |
9 | q.addEventListener('timeout', (event) => {
10 | t.ok(q)
11 | event.detail.next()
12 | })
13 |
14 | q.addEventListener('end', () => {
15 | const expected = ['two', 'three']
16 | t.equal(actual.length, expected.length)
17 |
18 | actual.forEach((a, i) => {
19 | const e = expected[i]
20 | t.equal(a, e)
21 | })
22 | })
23 |
24 | q.push((cb) => {
25 | // forget to call cb
26 | })
27 |
28 | q.push((cb) => {
29 | actual.push('two')
30 | cb()
31 | })
32 |
33 | q.push((cb) => {
34 | actual.push('three')
35 | cb()
36 | })
37 |
38 | q.start()
39 | })
40 |
41 | tap('job timeout', (t) => {
42 | t.plan(2)
43 | const q = new Queue({ timeout: 5 })
44 | let timeouts = 0
45 | const willTimeout = (cb) => {
46 | setTimeout(cb, 8)
47 | }
48 | const wontTimeout = (cb) => {
49 | setTimeout(cb, 8)
50 | }
51 |
52 | wontTimeout.timeout = 10
53 |
54 | q.addEventListener('timeout', (event) => {
55 | t.ok(q)
56 | timeouts++
57 | event.detail.next()
58 | })
59 |
60 | q.addEventListener('end', () => {
61 | t.equal(timeouts, 1)
62 | })
63 |
64 | q.push(willTimeout)
65 | q.push(wontTimeout)
66 |
67 | q.start()
68 | })
69 |
70 | tap('job-based opt-out of timeout', (t) => {
71 | t.plan(1)
72 | const q = new Queue({ timeout: 5 })
73 | let timeouts = 0
74 | const wontTimeout = (cb) => {
75 | setTimeout(cb, 8)
76 | }
77 |
78 | wontTimeout.timeout = undefined
79 |
80 | q.addEventListener('timeout', (event) => {
81 | t.fail('Job should not have timed-out')
82 | timeouts++
83 | event.detail.next()
84 | })
85 |
86 | q.addEventListener('end', () => {
87 | t.equal(timeouts, 0)
88 | })
89 |
90 | q.push(wontTimeout)
91 |
92 | q.start()
93 | })
94 |
95 | tap('timeout auto-continue', (t) => {
96 | t.plan(3)
97 | const actual = []
98 | const q = new Queue({ timeout: 10 })
99 |
100 | q.addEventListener('end', () => {
101 | const expected = ['two', 'three']
102 | t.equal(actual.length, expected.length)
103 |
104 | actual.forEach((a, i) => {
105 | const e = expected[i]
106 | t.equal(a, e)
107 | })
108 | })
109 |
110 | q.push(function (cb) {
111 | // forget to call cb
112 | })
113 |
114 | q.push(function (cb) {
115 | actual.push('two')
116 | cb()
117 | })
118 |
119 | q.push(function (cb) {
120 | actual.push('three')
121 | cb()
122 | })
123 |
124 | q.start()
125 | })
126 |
127 | tap('unref timeouts', (t) => {
128 | t.plan(3)
129 | const q = new Queue({ timeout: 99999 })
130 |
131 | q.push(function (cb) {
132 | t.pass()
133 | // forget to call cb
134 | })
135 |
136 | q.start()
137 |
138 | q.stop()
139 |
140 | setTimeout(() => {
141 | t.equal(q.pending, 1)
142 |
143 | q.end()
144 |
145 | t.equal(q.pending, 0)
146 | }, 10)
147 | })
148 |
149 | tap('From the timeout event it should be possible to find out the delayed promise', (t) => {
150 | t.plan(1)
151 | const q = new Queue({ timeout: 5 })
152 | let promise
153 |
154 | q.addEventListener('timeout',(event) => {
155 | t.equal(event.detail.job.promise, promise)
156 | })
157 |
158 | q.push(() => {
159 | promise = new Promise((resolve) => setTimeout(resolve, 10));
160 |
161 | return promise;
162 | })
163 |
164 | q.start()
165 | })
166 |
--------------------------------------------------------------------------------