time-queues 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,16 +29,21 @@ cd time-queues
29
29
  npm install
30
30
  ```
31
31
 
32
- ## Usage
32
+ ## Documentation
33
33
 
34
- The full documentation is available in the project's [wiki](https://github.com/uhop/time-queues/wiki). Below is a summary of the most important parts of the documentation:
34
+ The [project wiki](https://github.com/uhop/time-queues/wiki) provides comprehensive information about the `time-queues` library.
35
35
 
36
- ### Resource Management
36
+ ### Core Task Queue Classes
37
37
 
38
- - [Scheduler](https://github.com/uhop/time-queues/wiki/Scheduler): Time-based task scheduling
39
- - [Retainer](https://github.com/uhop/time-queues/wiki/Retainer): Manage resource lifecycle
38
+ - [MicroTask](https://github.com/uhop/time-queues/wiki/MicroTask): Base class for deferred execution
39
+ - [MicroTaskQueue](https://github.com/uhop/time-queues/wiki/MicroTaskQueue): Base class for task queues
40
+ - [ListQueue](https://github.com/uhop/time-queues/wiki/ListQueue): List-based queue implementation
41
+
42
+ ### Concurrency Control
43
+
44
+ - [LimitedQueue](https://github.com/uhop/time-queues/wiki/LimitedQueue): Queue with controlled concurrency
40
45
  - [Throttler](https://github.com/uhop/time-queues/wiki/Throttler): Control execution rate based on keys
41
- - [Counter](https://github.com/uhop/time-queues/wiki/Counter): Track the number of pending tasks asynchronously
46
+ - [Counter](https://github.com/uhop/time-queues/wiki/Counter): Track pending task counts
42
47
 
43
48
  ### Browser-Specific Components
44
49
 
@@ -46,6 +51,11 @@ The full documentation is available in the project's [wiki](https://github.com/u
46
51
  - [FrameQueue](https://github.com/uhop/time-queues/wiki/FrameQueue): Execute tasks during animation frames
47
52
  - [PageWatcher](https://github.com/uhop/time-queues/wiki/PageWatcher): Monitor and respond to page lifecycle changes
48
53
 
54
+ ### Scheduling & Timing
55
+
56
+ - [Scheduler](https://github.com/uhop/time-queues/wiki/Scheduler): Time-based task scheduling
57
+ - [Retainer](https://github.com/uhop/time-queues/wiki/Retainer): Resource lifecycle management
58
+
49
59
  ### Utility Functions
50
60
 
51
61
  - [defer()](<https://github.com/uhop/time-queues/wiki/defer()>): Execute tasks in the next tick
@@ -54,6 +64,65 @@ The full documentation is available in the project's [wiki](https://github.com/u
54
64
  - [debounce()](<https://github.com/uhop/time-queues/wiki/debounce()>): Delay function execution until input stabilizes
55
65
  - [sample()](<https://github.com/uhop/time-queues/wiki/sample()>): Execute function at regular intervals
56
66
  - [audit()](<https://github.com/uhop/time-queues/wiki/audit()>): Execute function after specified delay
67
+ - [batch()](<https://github.com/uhop/time-queues/wiki/batch()>): Execute async operations with controlled concurrency
68
+
69
+ ### Random Distribution Utilities
70
+
71
+ - [random-dist](https://github.com/uhop/time-queues/wiki/random-dist): Generate random numbers from various probability distributions
72
+ - [random-sleep](https://github.com/uhop/time-queues/wiki/random-sleep): Create randomized delays with various probability distributions
73
+
74
+ ## Getting Started
75
+
76
+ To get started with `time-queues`, install it via npm:
77
+
78
+ ```sh
79
+ npm install time-queues
80
+ ```
81
+
82
+ Then import the components you need in your project:
83
+
84
+ ```js
85
+ // Import specific components
86
+ import {Scheduler, repeat} from 'time-queues/Scheduler.js';
87
+ import idleQueue from 'time-queues/IdleQueue.js';
88
+ import defer from 'time-queues/defer.js';
89
+
90
+ // Use the components in your application
91
+ ```
92
+
93
+ For more information, see the documentation for each component in the wiki.
94
+
95
+ ## Browser-related notes
96
+
97
+ Internally it uses `list-toolkit` and leverages the following browser APIs:
98
+
99
+ - [requestIdleCallback()](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback)
100
+ - [requestAnimationFrame()](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame)
101
+ - [queueMicrotask()](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask)
102
+ - [setTimeout()](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout)
103
+ - Various events and properties.
104
+
105
+ There are many articles on the subject that detail how to leverage the APIs writing efficient applications.
106
+ Some of them are:
107
+
108
+ - [Background Tasks API](https://developer.mozilla.org/en-US/docs/Web/API/Background_Tasks_API)
109
+ - [Page Visibility API](https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API)
110
+ - [Page Lifecycle API](https://developer.chrome.com/docs/web-platform/page-lifecycle-api)
111
+
112
+ This package eliminates the need to write code that you'll write anyway following best practices.
113
+
114
+ ### Running a test web application
115
+
116
+ Don't forget to look at a test web application that uses the library. For that you should start a server:
117
+
118
+ ```sh
119
+ npm start
120
+ ```
121
+
122
+ And navigate to [http://localhost:3000/tests/web/](http://localhost:3000/tests/web/) &mdash;
123
+ don't forget to open the console and play around: switch tabs, make other window active,
124
+ navigate away and come back, and so on.
125
+ See how queues work in [tests/web/test.js](https://github.com/uhop/time-queues/blob/main/tests/web/test.js).
57
126
 
58
127
  ## License
59
128
 
@@ -61,6 +130,8 @@ This project is licensed under the BSD-3-Clause License.
61
130
 
62
131
  ## Release History
63
132
 
133
+ - 1.3.0 _Added `batch()` and `LimitedQueue` to run asynchronous operations with controlled concurrency, random distribuitions and random sleep functions, updated dependencies, minor improvements._
134
+ - 1.2.4 _Updated dependencies._
64
135
  - 1.2.3 _Updated dependencies._
65
136
  - 1.2.2 _`Counter`: separated old waiter from new waiters before notifying them._
66
137
  - 1.2.1 _Minor release: updated formal TS dependencies in `index.d.ts`._
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "time-queues",
3
- "version": "1.2.3",
3
+ "version": "1.3.0",
4
4
  "description": "Time queues to organize multitasking and scheduled tasks.",
5
5
  "type": "module",
6
6
  "types": "./src/index.d.ts",
@@ -12,8 +12,11 @@
12
12
  "test:bun": "tape6-bun --flags FO",
13
13
  "test:deno": "tape6-deno --flags FO",
14
14
  "test:proc": "tape6-proc --flags FO",
15
- "test:proc:bun": "bun run `npx tape6-proc --self` --flags FO",
16
- "test:proc:deno": "deno run -A `npx tape6-proc --self` --flags FO --runFileArgs -A",
15
+ "test:proc:bun": "bun run `tape6-proc --self` --flags FO",
16
+ "test:proc:deno": "deno run -A `tape6-proc --self` --flags FO -r -A",
17
+ "test:seq": "tape6-seq --flags FO",
18
+ "test:seq:bun": "bun run `tape6-seq --self` --flags FO",
19
+ "test:seq:deno": "deno run -A `tape6-seq --self` --flags FO",
17
20
  "ts-check": "tsc --noEmit",
18
21
  "ts-test": "tape6 --flags FO '/ts-tests/test-*.*ts'",
19
22
  "ts-test:bun": "tape6-bun --flags FO '/ts-tests/test-*.*ts'",
@@ -56,12 +59,12 @@
56
59
  }
57
60
  },
58
61
  "devDependencies": {
59
- "@types/node": "^25.0.10",
60
- "tape-six": "^1.4.4",
61
- "tape-six-proc": "^1.1.5",
62
+ "@types/node": "^25.2.3",
63
+ "tape-six": "^1.7.0",
64
+ "tape-six-proc": "^1.2.2",
62
65
  "typescript": "^5.9.3"
63
66
  },
64
67
  "dependencies": {
65
- "list-toolkit": "^2.2.4"
68
+ "list-toolkit": "^2.2.6"
66
69
  }
67
70
  }
@@ -2,7 +2,7 @@
2
2
  * A cancellation error that is thrown when a microtask is canceled.
3
3
  */
4
4
  export declare class CancelTaskError extends Error {
5
- constructor();
5
+ constructor(message: string = 'Task was canceled', options?: ErrorOptions);
6
6
  }
7
7
 
8
8
  export default CancelTaskError;
@@ -3,8 +3,8 @@
3
3
  'use strict';
4
4
 
5
5
  export class CancelTaskError extends Error {
6
- constructor() {
7
- super('Task was canceled');
6
+ constructor(message = 'Task was canceled', options) {
7
+ super(message, options);
8
8
  this.name = 'CancelTaskError';
9
9
  if (Error.captureStackTrace) {
10
10
  Error.captureStackTrace(this, CancelTaskError);
package/src/Counter.d.ts CHANGED
@@ -15,7 +15,6 @@ export declare class Counter {
15
15
 
16
16
  /**
17
17
  * Gets the current value of the counter.
18
- * @returns The current value of the counter.
19
18
  */
20
19
  get value(): number;
21
20
 
@@ -0,0 +1,104 @@
1
+ import {ListQueue, Task} from './ListQueue';
2
+
3
+ export {Task};
4
+
5
+ export declare class LimitedQueue extends ListQueue {
6
+ /**
7
+ * Whether the queue is paused.
8
+ */
9
+ paused: boolean;
10
+
11
+ /**
12
+ * The function that stops the queue.
13
+ * It is used internally by `pause()` and `resume()`.
14
+ */
15
+ stopQueue: (() => void) | null;
16
+
17
+ /**
18
+ * Creates a new list queue.
19
+ * @param limit The maximum number of tasks that can be run in parallel.
20
+ * @param paused Whether the queue should start paused.
21
+ */
22
+ constructor(limit: number, paused?: boolean);
23
+
24
+ /**
25
+ * Whether the queue is empty.
26
+ */
27
+ get isEmpty(): boolean;
28
+
29
+ /**
30
+ * Get the maximum number of tasks that can be run in parallel.
31
+ */
32
+ get taskLimit(): number;
33
+
34
+ /**
35
+ * Set the maximum number of tasks that can be run in parallel.
36
+ * @param limit The new maximum number of tasks that can be run in parallel. It can dynamically add more tasks if the current number of tasks is less than the new limit.
37
+ */
38
+ set taskLimit(limit: number);
39
+
40
+ /**
41
+ * Get the number of currently active tasks.
42
+ */
43
+ get activeTasks(): number;
44
+
45
+ /**
46
+ * Whether the queue is idle.
47
+ */
48
+ get isIdle(): boolean;
49
+
50
+ /**
51
+ * Wait for queue to become idle.
52
+ * @returns A promise that resolves when the queue becomes idle. If the queue is already idle, the promise is resolved immediately.
53
+ */
54
+ waitForIdle(): Promise<void>;
55
+
56
+ /**
57
+ * Enqueues a microtask.
58
+ * @param fn The function to execute when the microtask is scheduled.
59
+ * @returns The enqueued microtask.
60
+ */
61
+ enqueue(fn: () => unknown): Task;
62
+
63
+ /**
64
+ * Dequeues a microtask.
65
+ * @param task The microtask to dequeue.
66
+ * @returns The queue.
67
+ */
68
+ dequeue(task: Task): this;
69
+
70
+ /**
71
+ * Schedules a microtask.
72
+ * @param fn The function to execute. If `undefined` or `null`, the task's promise will be resolved with function's arguments. Otherwise, it is resolved with the function's return value.
73
+ * @returns The task object.
74
+ */
75
+ schedule(fn: (() => unknown) | null | undefined): Task;
76
+
77
+ /**
78
+ * Clears the queue.
79
+ * @returns The queue.
80
+ */
81
+ clear(): this;
82
+
83
+ /**
84
+ * Pauses the queue.
85
+ * @returns The queue.
86
+ */
87
+ pause(): this;
88
+
89
+ /**
90
+ * Resumes the queue.
91
+ * @returns The queue.
92
+ */
93
+ resume(): this;
94
+
95
+ /**
96
+ * Starts the queue.
97
+ * It is used internally by `resume()`.
98
+ * It is meant to be overridden in subclasses.
99
+ * @returns The function that stops the queue.
100
+ */
101
+ startQueue(): (() => void) | null;
102
+ }
103
+
104
+ export default LimitedQueue;
@@ -0,0 +1,78 @@
1
+ // @ts-self-types="./LimitedQueue.d.ts"
2
+
3
+ import ListQueue from './ListQueue.js';
4
+
5
+ export class LimitedQueue extends ListQueue {
6
+ #taskLimit;
7
+ #activeTasks;
8
+ #idleWaiters;
9
+
10
+ constructor(limit, paused) {
11
+ super(paused);
12
+ this.#taskLimit = limit;
13
+ this.#activeTasks = 0;
14
+ this.#idleWaiters = [];
15
+ }
16
+
17
+ get taskLimit() {
18
+ return this.#taskLimit;
19
+ }
20
+
21
+ set taskLimit(limit) {
22
+ this.#taskLimit = Math.max(1, limit);
23
+ this.#processTasks();
24
+ }
25
+
26
+ get activeTasks() {
27
+ return this.#activeTasks;
28
+ }
29
+
30
+ get isIdle() {
31
+ return !this.#activeTasks && this.list.isEmpty;
32
+ }
33
+
34
+ waitForIdle() {
35
+ return new Promise(resolve => {
36
+ if (this.isIdle) {
37
+ resolve();
38
+ } else {
39
+ this.#idleWaiters.push(resolve);
40
+ }
41
+ });
42
+ }
43
+
44
+ startQueue() {
45
+ this.#processTasks();
46
+ return null;
47
+ }
48
+
49
+ #processTasks() {
50
+ if (this.paused) return;
51
+ if (this.isIdle) {
52
+ const waiters = this.#idleWaiters;
53
+ this.#idleWaiters = [];
54
+ waiters.forEach(resolve => resolve());
55
+ return;
56
+ }
57
+ while (this.#activeTasks < this.#taskLimit && !this.list.isEmpty) {
58
+ const task = this.list.popFront();
59
+ ++this.#activeTasks;
60
+ LimitedQueue.wrap(() => task.fn({task, queue: this})).finally(() => {
61
+ --this.#activeTasks;
62
+ this.#processTasks();
63
+ });
64
+ }
65
+ }
66
+
67
+ static wrap(fn) {
68
+ return new Promise((resolve, reject) => {
69
+ try {
70
+ resolve(fn());
71
+ } catch (error) {
72
+ reject(error);
73
+ }
74
+ });
75
+ }
76
+ }
77
+
78
+ export default LimitedQueue;
@@ -82,9 +82,4 @@ export declare class ListQueue extends MicroTaskQueue {
82
82
  */
83
83
  export declare type Task = MicroTask;
84
84
 
85
- /**
86
- * A task for list queues with a promise.
87
- */
88
- export declare type TaskWithPromise = MicroTaskWithPromise;
89
-
90
85
  export default ListQueue;
@@ -42,9 +42,10 @@ export declare class MicroTask {
42
42
  * Cancels the microtask, if a promise is created.
43
43
  * If the microtask is canceled, the promise will be rejected with a CancelTaskError.
44
44
  * It can be overridden in subclasses.
45
+ * @param error The optional error to use as the cause of the cancellation.
45
46
  * @returns The microtask.
46
47
  */
47
- cancel(): this;
48
+ cancel(error?: Error): this;
48
49
  }
49
50
 
50
51
  export default MicroTask;
package/src/MicroTask.js CHANGED
@@ -1,13 +1,12 @@
1
1
  // @ts-self-types="./MicroTask.d.ts"
2
2
 
3
- 'use strict';
4
-
5
3
  import CancelTaskError from './CancelTaskError.js';
6
4
 
7
5
  export class MicroTask {
8
6
  #promise;
9
7
  #resolve;
10
8
  #reject;
9
+ #settled;
11
10
  constructor(fn) {
12
11
  this.fn = fn;
13
12
  this.#promise = null;
@@ -18,26 +17,42 @@ export class MicroTask {
18
17
  get promise() {
19
18
  return this.#promise;
20
19
  }
20
+ get settled() {
21
+ return this.#settled;
22
+ }
21
23
  makePromise() {
22
- this.#promise = new Promise((resolve, reject) => {
23
- this.#resolve = resolve;
24
- this.#reject = reject;
25
- });
24
+ if (this.#promise) return this;
25
+ if (typeof Promise.withResolvers == 'function') {
26
+ ({
27
+ promise: this.#promise,
28
+ resolve: this.#resolve,
29
+ reject: this.#reject
30
+ } = Promise.withResolvers());
31
+ } else {
32
+ this.#promise = new Promise((resolve, reject) => {
33
+ this.#resolve = resolve;
34
+ this.#reject = reject;
35
+ });
36
+ }
26
37
  return this;
27
38
  }
28
39
  resolve(value) {
29
- if (!this.#resolve) return;
30
- this.#resolve(value);
31
- this.#resolve = null;
32
- this.#reject = null;
40
+ if (this.#resolve) {
41
+ this.#resolve(value);
42
+ this.#resolve = null;
43
+ this.#reject = null;
44
+ this.#settled = true;
45
+ }
33
46
  return this;
34
47
  }
35
- cancel() {
36
- if (!this.#reject) return;
48
+ cancel(error) {
37
49
  this.isCanceled = true;
38
- this.#reject(new CancelTaskError());
39
- this.#resolve = null;
40
- this.#reject = null;
50
+ if (this.#reject) {
51
+ this.#reject(new CancelTaskError(undefined, error ? {cause: error} : undefined));
52
+ this.#resolve = null;
53
+ this.#reject = null;
54
+ this.#settled = true;
55
+ }
41
56
  return this;
42
57
  }
43
58
  }
@@ -2,68 +2,72 @@ import MicroTask from './MicroTask';
2
2
 
3
3
  /**
4
4
  * A queue of microtasks that will be executed when scheduled.
5
- * It is a base class for other task queues.
5
+ * It serves as a base class for other task queues.
6
6
  */
7
7
  export declare class MicroTaskQueue {
8
8
  /**
9
- * Whether the queue is paused.
9
+ * Whether the queue is currently paused.
10
+ * When paused, new tasks are queued but not executed immediately.
10
11
  */
11
12
  paused: boolean;
12
13
 
13
14
  /**
14
15
  * Creates a new microtask queue.
15
- * @param paused Whether the queue should start paused.
16
+ * @param paused Whether the queue should start in a paused state.
16
17
  */
17
18
  constructor(paused?: boolean);
18
19
 
19
20
  /**
20
21
  * Whether the queue is empty.
21
- * It is meant to be overridden in subclasses.
22
+ * This property should be overridden in subclasses to provide actual implementation.
22
23
  */
23
24
  get isEmpty(): boolean;
24
25
 
25
26
  /**
26
- * Enqueues a microtask.
27
- * It is meant to be overridden in subclasses.
27
+ * Enqueues a microtask for execution.
28
+ * This method should be overridden in subclasses to provide actual implementation.
28
29
  * @param fn The function to execute when the microtask is scheduled.
29
30
  * @returns The enqueued microtask.
30
31
  */
31
32
  enqueue(fn: () => unknown): MicroTask;
32
33
 
33
34
  /**
34
- * Dequeues a microtask.
35
- * It is meant to be overridden in subclasses.
35
+ * Dequeues a microtask from the queue.
36
+ * This method should be overridden in subclasses to provide actual implementation.
36
37
  * @param task The microtask to dequeue.
37
- * @returns The queue.
38
+ * @returns The queue instance for chaining.
38
39
  */
39
40
  dequeue(task: MicroTask): this;
40
41
 
41
42
  /**
42
- * Schedules a microtask with a promise.
43
- * It can be overridden in subclasses, if more arguments are needed.
43
+ * Schedules a microtask with a promise for execution.
44
+ * This can be overridden in subclasses if more arguments are needed.
44
45
  * @param fn The function to execute when the microtask is scheduled, it can be an async function.
46
+ * @param args Additional arguments to pass to `enqueue()`. It is there to accommodate custom implementations in subclasses.
45
47
  * @returns The scheduled microtask.
46
48
  */
47
- schedule(fn: (() => unknown) | null | undefined): MicroTask;
49
+ schedule(fn: (() => unknown) | null | undefined, ...args: unknown[]): MicroTask;
48
50
 
49
51
  /**
50
- * Clears the queue.
51
- * It is meant to be overridden in subclasses.
52
- * @returns The queue.
52
+ * Clears all tasks from the queue.
53
+ * This method should be overridden in subclasses to provide actual implementation.
54
+ * @returns The queue instance for chaining.
53
55
  */
54
56
  clear(): this;
55
57
 
56
58
  /**
57
59
  * Pauses the queue.
58
- * It is meant to be overridden in subclasses.
59
- * @returns The queue.
60
+ * When paused, new tasks are queued but not executed immediately.
61
+ * This method should be overridden in subclasses to provide actual implementation.
62
+ * @returns The queue instance for chaining.
60
63
  */
61
64
  pause(): this;
62
65
 
63
66
  /**
64
67
  * Resumes the queue.
65
- * It is meant to be overridden in subclasses.
66
- * @returns The queue.
68
+ * When resumed, queued tasks will be executed according to their scheduling.
69
+ * This method should be overridden in subclasses to provide actual implementation.
70
+ * @returns The queue instance for chaining.
67
71
  */
68
72
  resume(): this;
69
73
  }
@@ -35,17 +35,18 @@ export class MicroTaskQueue {
35
35
  schedule(fn, ...args) {
36
36
  fn ||= MicroTaskQueue.returnArgs;
37
37
  const task = this.enqueue(
38
- (...args) => {
38
+ function (...args) {
39
+ this.makePromise();
39
40
  try {
40
- task.resolve(fn(...args));
41
+ this.resolve(fn(...args));
41
42
  } catch (error) {
42
- task.cancel();
43
+ this.cancel(error);
43
44
  }
45
+ return this.promise;
44
46
  },
45
47
  ...args
46
48
  );
47
49
  task.makePromise();
48
- task.fn = fn;
49
50
  return task;
50
51
  }
51
52
  static returnArgs(...args) {
@@ -55,7 +55,6 @@ export declare class Task extends MicroTask {
55
55
  /**
56
56
  * Cancels the microtask, if a promise is created.
57
57
  * If the microtask is canceled, the promise will be rejected with a CancelTaskError.
58
- * It can be overridden in subclasses.
59
58
  * @returns The microtask.
60
59
  */
61
60
  cancel(): this;
@@ -67,43 +66,47 @@ export declare class Task extends MicroTask {
67
66
  export declare class Scheduler extends MicroTaskQueue {
68
67
  /**
69
68
  * Whether the scheduler is paused.
69
+ * When paused, new tasks are queued but not executed immediately.
70
70
  */
71
71
  paused: boolean;
72
72
 
73
73
  /**
74
74
  * The tolerance for comparing starting time of tasks.
75
+ * This allows for small timing differences in task execution.
75
76
  */
76
77
  tolerance: number;
77
78
 
78
79
  /**
79
80
  * Creates a new scheduler.
80
- * @param paused Whether the scheduler should start paused.
81
- * @param tolerance The tolerance for comparing starting time of tasks.
81
+ * @param paused Whether the scheduler should start in a paused state.
82
+ * @param tolerance The tolerance for comparing starting time of tasks (default is 4ms).
82
83
  */
83
- constructor(paused?: boolean, tolerance: number = 4);
84
+ constructor(paused?: boolean, tolerance?: number);
84
85
 
85
86
  /**
86
87
  * Whether the scheduler is empty.
88
+ * Returns true if there are no tasks scheduled for execution.
87
89
  */
88
90
  get isEmpty(): boolean;
89
91
 
90
92
  /**
91
93
  * The next scheduled time or `Infinity` if the scheduler is empty.
94
+ * Represents the time when the next task is scheduled to execute.
92
95
  */
93
96
  get nextTime(): number;
94
97
 
95
98
  /**
96
- * Enqueues a task.
99
+ * Enqueues a task for future execution.
97
100
  * @param fn The function to execute.
98
101
  * @param delay The delay before the task is executed. It can be a number of milliseconds or a `Date` object as an absolute time.
99
- * @returns The task object.
102
+ * @returns The task object that was enqueued.
100
103
  */
101
104
  enqueue(fn: ({task: Task, scheduler: Scheduler}) => unknown, delay: number | Date): Task;
102
105
 
103
106
  /**
104
107
  * Removes a task from the scheduler.
105
108
  * @param task The task to remove.
106
- * @returns The scheduler object.
109
+ * @returns The scheduler object for chaining.
107
110
  */
108
111
  dequeue(task: Task): this;
109
112
 
@@ -111,7 +114,7 @@ export declare class Scheduler extends MicroTaskQueue {
111
114
  * Schedules a task to run in the future.
112
115
  * @param fn The function to execute. If `undefined` or `null`, the task's promise will be resolved with function's arguments. Otherwise, it is resolved with the function's return value.
113
116
  * @param delay The delay before the task is executed. It can be a number of milliseconds or a `Date` object as an absolute time.
114
- * @returns The task object.
117
+ * @returns The task object that was scheduled.
115
118
  */
116
119
  schedule(
117
120
  fn: (({task: Task, scheduler: Scheduler}) => unknown) | null | undefined,
@@ -119,20 +122,22 @@ export declare class Scheduler extends MicroTaskQueue {
119
122
  ): Task;
120
123
 
121
124
  /**
122
- * Clears the queue.
123
- * @returns The queue.
125
+ * Clears all tasks from the scheduler.
126
+ * @returns The scheduler instance for chaining.
124
127
  */
125
128
  clear(): this;
126
129
 
127
130
  /**
128
- * Pauses the queue.
129
- * @returns The queue.
131
+ * Pauses the scheduler.
132
+ * When paused, new tasks are queued but not executed immediately.
133
+ * @returns The scheduler instance for chaining.
130
134
  */
131
135
  pause(): this;
132
136
 
133
137
  /**
134
- * Resumes the queue.
135
- * @returns The queue.
138
+ * Resumes the scheduler.
139
+ * When resumed, queued tasks will be executed according to their scheduling.
140
+ * @returns The scheduler instance for chaining.
136
141
  */
137
142
  resume(): this;
138
143
  }
package/src/batch.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Runs asynchronous operations in parallel, no more than a specified number at a time.
3
+ * It takes an array of functions, which return promises when invoked without arguments.
4
+ * All other non-function values are passed as-is and promises are resolved.
5
+ * Modelled after Promise.all().
6
+ *
7
+ * @param fns An array of parameterless functions (asynchronous or not), promises, or values
8
+ * @param limit How many asynchronous operations to run in parallel (default is 4)
9
+ * @returns A promise that resolves when all functions have completed to an array of results
10
+ */
11
+ export declare function batch(
12
+ fns: ((() => PromiseLike<unknown>) | PromiseLike<unknown> | unknown)[],
13
+ limit?: number
14
+ ): Promise<unknown[]>;
15
+
16
+ export default batch;
package/src/batch.js ADDED
@@ -0,0 +1,41 @@
1
+ // @ts-self-types="./batch.d.ts"
2
+
3
+ 'use strict';
4
+
5
+ const wrap = value => {
6
+ if (typeof value == 'function') return Promise.resolve(value());
7
+ if (value && typeof value.then == 'function') return value; // thenable
8
+ return Promise.resolve(value);
9
+ };
10
+
11
+ export const batch = (fns, limit = 4) => {
12
+ if (limit < 1) limit = 1;
13
+
14
+ const result = [];
15
+ let next = limit,
16
+ available = limit;
17
+
18
+ const saveResult = (index, resolve, reject) => value => {
19
+ result[index] = value;
20
+ if (next < fns.length) {
21
+ wrap(fns[next]).then(saveResult(next, resolve, reject), reject);
22
+ ++next;
23
+ } else {
24
+ ++available;
25
+ if (available === limit) {
26
+ // we are done
27
+ resolve(result);
28
+ }
29
+ }
30
+ };
31
+
32
+ return new Promise((resolve, reject) => {
33
+ // start the pump
34
+ for (let i = 0, n = Math.min(fns.length, limit); i < n; ++i) {
35
+ wrap(fns[i]).then(saveResult(i, resolve, reject), reject);
36
+ --available;
37
+ }
38
+ });
39
+ };
40
+
41
+ export default batch;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Generate a random number from a uniform distribution.
3
+ * @param min The minimum value of the distribution.
4
+ * @param max The maximum value of the distribution.
5
+ * @returns A random number from the uniform distribution.
6
+ */
7
+ export declare function uniform(min: number, max: number): number;
8
+
9
+ /**
10
+ * Generate a random number from a normal distribution.
11
+ * @param mean The mean of the distribution.
12
+ * @param stdDev The standard deviation of the distribution.
13
+ * @param skewness The skewness of the distribution.
14
+ * @returns A random number from the normal distribution.
15
+ */
16
+ export declare function normal(mean: number, stdDev: number, skewness: number = 0): number;
17
+
18
+ /**
19
+ * Generate a random number from an exponential distribution.
20
+ * @param lambda The rate parameter of the distribution.
21
+ * @returns A random number from the exponential distribution.
22
+ */
23
+ export declare function expo(lambda: number): number;
24
+
25
+ /**
26
+ * Generate a random number from a Pareto distribution.
27
+ * @param min The minimum value of the distribution.
28
+ * @param alpha The shape parameter of the distribution.
29
+ * @returns A random number from the Pareto distribution.
30
+ */
31
+ export declare function pareto(min: number, alpha: number): number;
@@ -0,0 +1,27 @@
1
+ // @ts-self-types="./random-dist.d.ts"
2
+
3
+ export const uniform = (min, max) => {
4
+ const range = Math.abs(max - min);
5
+ return Math.random() * range + Math.min(min, max);
6
+ };
7
+
8
+ export const normal = (mean, stdDev, skewness = 0) => {
9
+ let u = 0,
10
+ v = 0;
11
+ while (!u) u = Math.random(); // Converting [0,1) to (0,1)
12
+ while (!v) v = Math.random();
13
+ let z = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
14
+ if (!skewness) return z * stdDev + mean;
15
+ const delta = skewness / Math.sqrt(1 + skewness * skewness),
16
+ x = delta * z + Math.sqrt(1 - delta * delta) * v;
17
+ z = z >= 0 ? x : -x;
18
+ return z * stdDev + mean;
19
+ };
20
+
21
+ export const expo = lambda => {
22
+ return -Math.log(1 - Math.random()) / lambda;
23
+ };
24
+
25
+ export const pareto = (min, alpha) => {
26
+ return min / Math.pow(1 - Math.random(), 1 / alpha);
27
+ };
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Random sleep function modelled after `sleep()`.
3
+ */
4
+ export type RandomSleepFunction = () => Promise<void>;
5
+
6
+ /**
7
+ * Creates a random sleep function that uses a uniform distribution.
8
+ * @param min The minimum delay in milliseconds.
9
+ * @param max The maximum delay in milliseconds.
10
+ * @returns A sleep function.
11
+ */
12
+ export declare function randomUniformSleep(min: number, max: number): RandomSleepFunction;
13
+
14
+ /**
15
+ * Creates a random sleep function that uses a normal distribution.
16
+ * @param mean The mean delay in milliseconds.
17
+ * @param stdDev The standard deviation of the delay in milliseconds.
18
+ * @param skewness The skewness of the delay distribution.
19
+ * @returns A sleep function.
20
+ */
21
+ export declare function randomNormalSleep(
22
+ mean: number,
23
+ stdDev: number,
24
+ skewness: number = 0
25
+ ): RandomSleepFunction;
26
+
27
+ /**
28
+ * Creates a random sleep function that uses an exponential distribution.
29
+ * @param rate The rate parameter of the exponential distribution (how many times per `range` an event occurs).
30
+ * @param range The range of the exponential distribution.
31
+ * @param base The base of the exponential distribution.
32
+ * @returns A sleep function.
33
+ */
34
+ export declare function randomExpoSleep(
35
+ rate: number,
36
+ range: number,
37
+ base: number = 0
38
+ ): RandomSleepFunction;
39
+
40
+ /**
41
+ * Creates a random sleep function that uses a Pareto distribution.
42
+ * @param min The minimum delay in milliseconds.
43
+ * @param ratio The ratio of the Pareto distribution, e.g., 0.8 for the 80/20 Pareto rule. Should be a value between 0.5 and 1.
44
+ * @returns A sleep function.
45
+ * @throws Error if the ratio is not between 0.5 and 1.
46
+ */
47
+ export declare function randomParetoSleep(min: number, ratio: number = 0.8): RandomSleepFunction;
48
+
49
+ /**
50
+ * A simple sleep function that uses a uniform distribution.
51
+ * @param max The maximum delay in milliseconds.
52
+ * @param min The minimum delay in milliseconds.
53
+ * @returns A promise that resolves after the delay.
54
+ */
55
+ export declare function randomSleep(max: number, min: number = 0): Promise<void>;
56
+
57
+ export default randomSleep;
@@ -0,0 +1,27 @@
1
+ // @ts-self-types="./random-sleep.d.ts"
2
+
3
+ import sleep from './sleep.js';
4
+ import {uniform, normal, expo, pareto} from './random-dist.js';
5
+
6
+ export const randomUniformSleep = (min, max) => () => sleep(uniform(min, max));
7
+
8
+ export const randomNormalSleep =
9
+ (mean, stdDev, skewness = 0) =>
10
+ () =>
11
+ sleep(normal(mean, stdDev, skewness));
12
+
13
+ export const randomExpoSleep =
14
+ (rate, range, base = 0) =>
15
+ () =>
16
+ sleep(range * expo(rate) + base);
17
+
18
+ export const randomParetoSleep = (min, ratio = 0.8) => {
19
+ if (ratio <= 0.5 || ratio >= 1 || isNaN(ratio))
20
+ throw new Error('ratio must be greater than 0.5 and less than 1');
21
+ const alpha = 1 / (1 - Math.log(ratio) / Math.log(1 - ratio));
22
+ return () => sleep(pareto(min, alpha));
23
+ };
24
+
25
+ export const randomSleep = (max, min = 0) => sleep(uniform(min, max));
26
+
27
+ export default randomSleep;
package/src/sleep.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Suspends the execution for a specified time.
3
3
  *
4
- * @param ms The time to suspend the execution for, in milliseconds or a date as an absolute time.
4
+ * @param ms The time to suspend the execution for, in milliseconds or a Date as an absolute time.
5
5
  * @returns A promise that resolves after the specified time.
6
6
  */
7
7
  export declare function sleep(ms: number | Date): Promise<void>;
package/src/throttle.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Throttles a function by ensuring it is not called more often than the specified interval.
3
- * The first call calls the function and starts a timeout. Until the timeout expires, any subsequent calls are ignored.
3
+ * The first call calls the function and starts a timeout. Until the timeout expires,
4
+ * any subsequent calls are ignored.
4
5
  * This function is similar to `audit()`, but the first seen arguments are passed to the function.
5
6
  *
6
7
  * @param fn The function to throttle.
@@ -20,18 +20,14 @@ export declare function remove(fn: () => void): boolean;
20
20
  * @param fn The function to schedule.
21
21
  * @returns A promise that resolves when the DOM is loaded.
22
22
  */
23
- export declare function scheduleWhenDomLoaded<R extends unknown>(
24
- fn: () => R
25
- ): Promise<Awaited<R>>;
23
+ export declare function scheduleWhenDomLoaded<R extends unknown>(fn: () => R): Promise<Awaited<R>>;
26
24
 
27
25
  /**
28
- * Schedules a function to be called when the DOM is loaded.
26
+ * Resolves a promise when the DOM is loaded.
29
27
  *
30
- * @param fn The function to schedule.
28
+ * @param fn `null` or `undefined`.
31
29
  * @returns A promise that resolves when the DOM is loaded.
32
30
  */
33
- export declare function scheduleWhenDomLoaded(
34
- fn: null | undefined
35
- ): Promise<void>;
31
+ export declare function scheduleWhenDomLoaded(fn?: null): Promise<void>;
36
32
 
37
33
  export default whenDomLoaded;
@@ -34,19 +34,15 @@ export const whenDomLoaded = fn => {
34
34
  if (wasEmpty) document.addEventListener('DOMContentLoaded', handleDomLoaded);
35
35
  };
36
36
 
37
- const returnArgs = (...args) => args;
38
-
39
- export const scheduleWhenDomLoaded = fn => {
40
- fn ||= returnArgs;
41
- return new Promise((resolve, reject) => {
37
+ export const scheduleWhenDomLoaded = fn =>
38
+ new Promise((resolve, reject) => {
42
39
  whenDomLoaded(() => {
43
40
  try {
44
- resolve(fn());
41
+ resolve(fn?.());
45
42
  } catch (error) {
46
43
  reject(error);
47
44
  }
48
45
  });
49
46
  });
50
- };
51
47
 
52
48
  export default whenDomLoaded;
@@ -20,18 +20,14 @@ export declare function remove(fn: () => void): boolean;
20
20
  * @param fn The function to schedule.
21
21
  * @returns A promise that resolves when the document is loaded.
22
22
  */
23
- export declare function scheduleWhenLoaded<R extends unknown>(
24
- fn: () => R
25
- ): Promise<Awaited<R>>;
23
+ export declare function scheduleWhenLoaded<R extends unknown>(fn: () => R): Promise<Awaited<R>>;
26
24
 
27
25
  /**
28
- * Schedules a function to be called when the document is loaded.
26
+ * Resolves a promise when the document is loaded.
29
27
  *
30
- * @param fn The function to schedule.
28
+ * @param fn `null` or `undefined`.
31
29
  * @returns A promise that resolves when the document is loaded.
32
30
  */
33
- export declare function scheduleWhenLoaded(
34
- fn: null | undefined
35
- ): Promise<void>;
31
+ export declare function scheduleWhenLoaded(fn?: null): Promise<void>;
36
32
 
37
33
  export default whenLoaded;
@@ -32,19 +32,15 @@ export const whenLoaded = fn => {
32
32
  if (wasEmpty) window.addEventListener('load', handleLoaded);
33
33
  };
34
34
 
35
- const returnArgs = (...args) => args;
36
-
37
- export const scheduleWhenLoaded = fn => {
38
- fn ||= returnArgs;
39
- return new Promise((resolve, reject) => {
35
+ export const scheduleWhenLoaded = fn =>
36
+ new Promise((resolve, reject) => {
40
37
  whenLoaded(() => {
41
38
  try {
42
- resolve(fn());
39
+ resolve(fn?.());
43
40
  } catch (error) {
44
41
  reject(error);
45
42
  }
46
43
  });
47
44
  });
48
- };
49
45
 
50
46
  export default whenLoaded;