Greasy Fork 支持简体中文。

queue

Asynchronous function queue with adjustable concurrency.

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/482311/1297431/queue.js

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