queue

Asynchronous function queue with adjustable concurrency.

目前为 2023-12-15 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/482311/1296526/queue.js

  1. // queue v7.0.0
  2. // Source Code: https://github.com/jessetane/queue/tree/7.0.0
  3. // Licensed under MIT License
  4.  
  5. const { QueueEvent, Queue } = (() =>
  6. {
  7.  
  8.  
  9. const has = Object.prototype.hasOwnProperty
  10.  
  11. /**
  12. * Since CustomEvent is only supported in nodejs since version 19,
  13. * you have to create your own class instead of using CustomEvent
  14. * @see https://github.com/nodejs/node/issues/40678
  15. * */
  16. class QueueEvent extends Event {
  17. constructor (name, detail) {
  18. super(name)
  19. this.detail = detail
  20. }
  21. }
  22.  
  23. class Queue extends EventTarget {
  24. constructor (options = {}) {
  25. super()
  26. const { concurrency = Infinity, timeout = 0, autostart = false, results = null } = options
  27. this.concurrency = concurrency
  28. this.timeout = timeout
  29. this.autostart = autostart
  30. this.results = results
  31. this.pending = 0
  32. this.session = 0
  33. this.running = false
  34. this.jobs = []
  35. this.timers = []
  36. this.addEventListener('error', this._errorHandler)
  37. }
  38.  
  39. _errorHandler (evt) {
  40. this.end(evt.detail.error)
  41. }
  42.  
  43. pop () {
  44. return this.jobs.pop()
  45. }
  46.  
  47. shift () {
  48. return this.jobs.shift()
  49. }
  50.  
  51. indexOf (searchElement, fromIndex) {
  52. return this.jobs.indexOf(searchElement, fromIndex)
  53. }
  54.  
  55. lastIndexOf (searchElement, fromIndex) {
  56. if (fromIndex !== undefined) return this.jobs.lastIndexOf(searchElement, fromIndex)
  57. return this.jobs.lastIndexOf(searchElement)
  58. }
  59.  
  60. slice (start, end) {
  61. this.jobs = this.jobs.slice(start, end)
  62. return this
  63. }
  64.  
  65. reverse () {
  66. this.jobs.reverse()
  67. return this
  68. }
  69.  
  70. push (...workers) {
  71. const methodResult = this.jobs.push(...workers)
  72. if (this.autostart) this._start()
  73. return methodResult
  74. }
  75.  
  76. unshift (...workers) {
  77. const methodResult = this.jobs.unshift(...workers)
  78. if (this.autostart) this._start()
  79. return methodResult
  80. }
  81.  
  82. splice (start, deleteCount, ...workers) {
  83. this.jobs.splice(start, deleteCount, ...workers)
  84. if (this.autostart) this._start()
  85. return this
  86. }
  87.  
  88. get length () {
  89. return this.pending + this.jobs.length
  90. }
  91.  
  92. start (callback) {
  93. if (this.running) throw new Error('already started')
  94. let awaiter
  95. if (callback) {
  96. this._addCallbackToEndEvent(callback)
  97. } else {
  98. awaiter = this._createPromiseToEndEvent()
  99. }
  100. this._start()
  101. return awaiter
  102. }
  103.  
  104. _start () {
  105. this.running = true
  106. if (this.pending >= this.concurrency) {
  107. return
  108. }
  109. if (this.jobs.length === 0) {
  110. if (this.pending === 0) {
  111. this.done()
  112. }
  113. return
  114. }
  115. const job = this.jobs.shift()
  116. const session = this.session
  117. const timeout = (job !== undefined) && has.call(job, 'timeout') ? job.timeout : this.timeout
  118. let once = true
  119. let timeoutId = null
  120. let didTimeout = false
  121. let resultIndex = null
  122. const next = (error, ...result) => {
  123. if (once && this.session === session) {
  124. once = false
  125. this.pending--
  126. if (timeoutId !== null) {
  127. this.timers = this.timers.filter(tID => tID !== timeoutId)
  128. clearTimeout(timeoutId)
  129. }
  130. if (error) {
  131. this.dispatchEvent(new QueueEvent('error', { error, job }))
  132. } else if (!didTimeout) {
  133. if (resultIndex !== null && this.results !== null) {
  134. this.results[resultIndex] = [...result]
  135. }
  136. this.dispatchEvent(new QueueEvent('success', { result: [...result], job }))
  137. }
  138. if (this.session === session) {
  139. if (this.pending === 0 && this.jobs.length === 0) {
  140. this.done()
  141. } else if (this.running) {
  142. this._start()
  143. }
  144. }
  145. }
  146. }
  147. if (timeout) {
  148. timeoutId = setTimeout(() => {
  149. didTimeout = true
  150. this.dispatchEvent(new QueueEvent('timeout', { next, job }))
  151. next()
  152. }, timeout)
  153. this.timers.push(timeoutId)
  154. }
  155. if (this.results != null) {
  156. resultIndex = this.results.length
  157. this.results[resultIndex] = null
  158. }
  159. this.pending++
  160. this.dispatchEvent(new QueueEvent('start', { job }))
  161. job.promise = job(next)
  162. if (job.promise !== undefined && typeof job.promise.then === 'function') {
  163. job.promise.then(function (result) {
  164. return next(undefined, result)
  165. }).catch(function (err) {
  166. return next(err || true)
  167. })
  168. }
  169. if (this.running && this.jobs.length > 0) {
  170. this._start()
  171. }
  172. }
  173.  
  174. stop () {
  175. this.running = false
  176. }
  177.  
  178. end (error) {
  179. this.clearTimers()
  180. this.jobs.length = 0
  181. this.pending = 0
  182. this.done(error)
  183. }
  184.  
  185. clearTimers () {
  186. this.timers.forEach(timer => {
  187. clearTimeout(timer)
  188. })
  189. this.timers = []
  190. }
  191.  
  192. _addCallbackToEndEvent (cb) {
  193. const onend = evt => {
  194. this.removeEventListener('end', onend)
  195. cb(evt.detail.error, this.results)
  196. }
  197. this.addEventListener('end', onend)
  198. }
  199.  
  200. _createPromiseToEndEvent () {
  201. return new Promise((resolve, reject) => {
  202. this._addCallbackToEndEvent((error, results) => {
  203. if (error) reject(error)
  204. else resolve(results)
  205. })
  206. })
  207. }
  208.  
  209. done (error) {
  210. this.session++
  211. this.running = false
  212. this.dispatchEvent(new QueueEvent('end', { error }))
  213. }
  214. }
  215.  
  216. return { QueueEvent, Queue };
  217.  
  218.  
  219. })();