@tanstack/pacer 0.1.0 → 0.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/dist/cjs/async-debouncer.cjs +112 -63
- package/dist/cjs/async-debouncer.cjs.map +1 -1
- package/dist/cjs/async-debouncer.d.cts +66 -25
- package/dist/cjs/async-queuer.cjs +198 -124
- package/dist/cjs/async-queuer.cjs.map +1 -1
- package/dist/cjs/async-queuer.d.cts +91 -49
- package/dist/cjs/async-rate-limiter.cjs +83 -55
- package/dist/cjs/async-rate-limiter.cjs.map +1 -1
- package/dist/cjs/async-rate-limiter.d.cts +55 -28
- package/dist/cjs/async-throttler.cjs +121 -70
- package/dist/cjs/async-throttler.cjs.map +1 -1
- package/dist/cjs/async-throttler.d.cts +75 -25
- package/dist/cjs/debouncer.cjs +45 -23
- package/dist/cjs/debouncer.cjs.map +1 -1
- package/dist/cjs/debouncer.d.cts +27 -12
- package/dist/cjs/index.cjs +2 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +2 -0
- package/dist/cjs/queuer.cjs +161 -101
- package/dist/cjs/queuer.cjs.map +1 -1
- package/dist/cjs/queuer.d.cts +80 -38
- package/dist/cjs/rate-limiter.cjs +52 -44
- package/dist/cjs/rate-limiter.cjs.map +1 -1
- package/dist/cjs/rate-limiter.d.cts +38 -46
- package/dist/cjs/throttler.cjs +57 -44
- package/dist/cjs/throttler.cjs.map +1 -1
- package/dist/cjs/throttler.d.cts +35 -23
- package/dist/cjs/types.d.cts +8 -0
- package/dist/cjs/utils.cjs +13 -0
- package/dist/cjs/utils.cjs.map +1 -0
- package/dist/cjs/utils.d.cts +1 -0
- package/dist/esm/async-debouncer.d.ts +66 -25
- package/dist/esm/async-debouncer.js +112 -63
- package/dist/esm/async-debouncer.js.map +1 -1
- package/dist/esm/async-queuer.d.ts +91 -49
- package/dist/esm/async-queuer.js +198 -124
- package/dist/esm/async-queuer.js.map +1 -1
- package/dist/esm/async-rate-limiter.d.ts +55 -28
- package/dist/esm/async-rate-limiter.js +83 -55
- package/dist/esm/async-rate-limiter.js.map +1 -1
- package/dist/esm/async-throttler.d.ts +75 -25
- package/dist/esm/async-throttler.js +121 -70
- package/dist/esm/async-throttler.js.map +1 -1
- package/dist/esm/debouncer.d.ts +27 -12
- package/dist/esm/debouncer.js +45 -23
- package/dist/esm/debouncer.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/queuer.d.ts +80 -38
- package/dist/esm/queuer.js +161 -101
- package/dist/esm/queuer.js.map +1 -1
- package/dist/esm/rate-limiter.d.ts +38 -46
- package/dist/esm/rate-limiter.js +52 -44
- package/dist/esm/rate-limiter.js.map +1 -1
- package/dist/esm/throttler.d.ts +35 -23
- package/dist/esm/throttler.js +57 -44
- package/dist/esm/throttler.js.map +1 -1
- package/dist/esm/types.d.ts +8 -0
- package/dist/esm/utils.d.ts +1 -0
- package/dist/esm/utils.js +13 -0
- package/dist/esm/utils.js.map +1 -0
- package/package.json +8 -1
- package/src/async-debouncer.ts +157 -88
- package/src/async-queuer.ts +266 -148
- package/src/async-rate-limiter.ts +123 -83
- package/src/async-throttler.ts +173 -89
- package/src/debouncer.ts +71 -42
- package/src/index.ts +2 -0
- package/src/queuer.ts +219 -114
- package/src/rate-limiter.ts +74 -88
- package/src/throttler.ts +83 -65
- package/src/types.ts +9 -0
- package/src/utils.ts +13 -0
package/src/async-queuer.ts
CHANGED
|
@@ -10,6 +10,16 @@ export interface AsyncQueuerOptions<TValue> {
|
|
|
10
10
|
* Maximum number of concurrent tasks to process
|
|
11
11
|
*/
|
|
12
12
|
concurrency?: number
|
|
13
|
+
/**
|
|
14
|
+
* Maximum time in milliseconds that an item can stay in the queue
|
|
15
|
+
* If not provided, items will never expire
|
|
16
|
+
*/
|
|
17
|
+
expirationDuration?: number
|
|
18
|
+
/**
|
|
19
|
+
* Function to determine if an item has expired
|
|
20
|
+
* If provided, this overrides the expirationDuration behavior
|
|
21
|
+
*/
|
|
22
|
+
getIsExpired?: (item: () => Promise<TValue>, addedAt: number) => boolean
|
|
13
23
|
/**
|
|
14
24
|
* Default position to get items from during processing
|
|
15
25
|
* @default 'front'
|
|
@@ -36,12 +46,24 @@ export interface AsyncQueuerOptions<TValue> {
|
|
|
36
46
|
item: () => Promise<TValue>,
|
|
37
47
|
queuer: AsyncQueuer<TValue>,
|
|
38
48
|
) => void
|
|
49
|
+
/**
|
|
50
|
+
* Callback fired whenever the queuer's running state changes
|
|
51
|
+
*/
|
|
52
|
+
onIsRunningChange?: (queuer: AsyncQueuer<TValue>) => void
|
|
39
53
|
/**
|
|
40
54
|
* Callback fired whenever an item is added or removed from the queuer
|
|
41
55
|
*/
|
|
42
|
-
|
|
56
|
+
onItemsChange?: (queuer: AsyncQueuer<TValue>) => void
|
|
57
|
+
/**
|
|
58
|
+
* Callback fired whenever an item is rejected from being added to the queuer
|
|
59
|
+
*/
|
|
60
|
+
onReject?: (item: () => Promise<TValue>, queuer: AsyncQueuer<TValue>) => void
|
|
43
61
|
/**
|
|
44
|
-
*
|
|
62
|
+
* Callback fired whenever an item expires in the queuer
|
|
63
|
+
*/
|
|
64
|
+
onExpire?: (item: () => Promise<TValue>, queuer: AsyncQueuer<TValue>) => void
|
|
65
|
+
/**
|
|
66
|
+
* Whether the queuer should start processing tasks immediately or not.
|
|
45
67
|
*/
|
|
46
68
|
started?: boolean
|
|
47
69
|
/**
|
|
@@ -53,64 +75,73 @@ export interface AsyncQueuerOptions<TValue> {
|
|
|
53
75
|
const defaultOptions: Required<AsyncQueuerOptions<any>> = {
|
|
54
76
|
addItemsTo: 'back',
|
|
55
77
|
concurrency: 1,
|
|
78
|
+
expirationDuration: Infinity,
|
|
79
|
+
getIsExpired: () => false,
|
|
56
80
|
getItemsFrom: 'front',
|
|
57
|
-
getPriority: (item) => (item as any)
|
|
81
|
+
getPriority: (item) => (item as any)?.priority ?? 0,
|
|
58
82
|
initialItems: [],
|
|
59
83
|
maxSize: Infinity,
|
|
60
84
|
onGetNextItem: () => {},
|
|
61
|
-
|
|
62
|
-
|
|
85
|
+
onIsRunningChange: () => {},
|
|
86
|
+
onItemsChange: () => {},
|
|
87
|
+
onReject: () => {},
|
|
88
|
+
onExpire: () => {},
|
|
89
|
+
started: true,
|
|
63
90
|
wait: 0,
|
|
64
91
|
}
|
|
65
92
|
|
|
66
93
|
/**
|
|
67
|
-
* A flexible asynchronous
|
|
94
|
+
* A flexible asynchronous queue that processes tasks with configurable concurrency control.
|
|
68
95
|
*
|
|
69
96
|
* Features:
|
|
70
|
-
* - Priority
|
|
97
|
+
* - Priority queue support via getPriority option
|
|
71
98
|
* - Configurable concurrency limit
|
|
72
99
|
* - Task success/error/completion callbacks
|
|
73
|
-
* - FIFO (First In First Out) or LIFO (Last In First Out)
|
|
100
|
+
* - FIFO (First In First Out) or LIFO (Last In First Out) queue behavior
|
|
74
101
|
* - Pause/resume task processing
|
|
75
102
|
* - Task cancellation
|
|
103
|
+
* - Item expiration to clear stale items from the queue
|
|
76
104
|
*
|
|
77
105
|
* Tasks are processed concurrently up to the configured concurrency limit. When a task completes,
|
|
78
106
|
* the next pending task is processed if below the concurrency limit.
|
|
79
107
|
*
|
|
80
108
|
* @example
|
|
81
109
|
* ```ts
|
|
82
|
-
* const
|
|
110
|
+
* const asyncQueuer = new AsyncQueuer<string>({ concurrency: 2 });
|
|
83
111
|
*
|
|
84
|
-
*
|
|
112
|
+
* asyncQueuer.addItem(async () => {
|
|
85
113
|
* return 'Hello';
|
|
86
114
|
* });
|
|
87
115
|
*
|
|
88
|
-
*
|
|
116
|
+
* asyncQueuer.start();
|
|
89
117
|
*
|
|
90
|
-
*
|
|
118
|
+
* asyncQueuer.onSuccess((result) => {
|
|
91
119
|
* console.log(result); // 'Hello'
|
|
92
120
|
* });
|
|
93
121
|
* ```
|
|
94
122
|
*/
|
|
95
123
|
export class AsyncQueuer<TValue> {
|
|
96
|
-
|
|
97
|
-
private
|
|
98
|
-
private
|
|
99
|
-
private
|
|
100
|
-
private
|
|
101
|
-
private
|
|
102
|
-
private
|
|
103
|
-
private
|
|
104
|
-
private
|
|
124
|
+
private _options: Required<AsyncQueuerOptions<TValue>>
|
|
125
|
+
private _activeItems: Set<() => Promise<TValue>> = new Set()
|
|
126
|
+
private _executionCount = 0
|
|
127
|
+
private _rejectionCount = 0
|
|
128
|
+
private _expirationCount = 0
|
|
129
|
+
private _items: Array<() => Promise<TValue>> = []
|
|
130
|
+
private _itemTimestamps: Array<number> = []
|
|
131
|
+
private _onErrorCallbacks: Array<(error: Error) => void> = []
|
|
132
|
+
private _onSettledCallbacks: Array<(result: TValue | Error) => void> = []
|
|
133
|
+
private _onSuccessCallbacks: Array<(result: TValue) => void> = []
|
|
134
|
+
private _pendingTick = false
|
|
135
|
+
private _running: boolean
|
|
105
136
|
|
|
106
137
|
constructor(initialOptions: AsyncQueuerOptions<TValue> = defaultOptions) {
|
|
107
|
-
this.
|
|
108
|
-
this.
|
|
138
|
+
this._options = { ...defaultOptions, ...initialOptions }
|
|
139
|
+
this._running = this._options.started
|
|
109
140
|
|
|
110
|
-
for (let i = 0; i < this.
|
|
111
|
-
const item = this.
|
|
112
|
-
const isLast = i === this.
|
|
113
|
-
this.addItem(item, this.
|
|
141
|
+
for (let i = 0; i < this._options.initialItems.length; i++) {
|
|
142
|
+
const item = this._options.initialItems[i]!
|
|
143
|
+
const isLast = i === this._options.initialItems.length - 1
|
|
144
|
+
this.addItem(item, this._options.addItemsTo, isLast)
|
|
114
145
|
}
|
|
115
146
|
}
|
|
116
147
|
|
|
@@ -118,32 +149,39 @@ export class AsyncQueuer<TValue> {
|
|
|
118
149
|
* Updates the queuer options
|
|
119
150
|
* Returns the new options state
|
|
120
151
|
*/
|
|
121
|
-
setOptions(
|
|
122
|
-
newOptions
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
152
|
+
setOptions(newOptions: Partial<AsyncQueuerOptions<TValue>>): void {
|
|
153
|
+
this._options = { ...this._options, ...newOptions }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Returns the current queuer options
|
|
158
|
+
*/
|
|
159
|
+
getOptions(): Required<AsyncQueuerOptions<TValue>> {
|
|
160
|
+
return this._options
|
|
126
161
|
}
|
|
127
162
|
|
|
128
163
|
/**
|
|
129
164
|
* Processes items in the queuer
|
|
130
165
|
*/
|
|
131
|
-
|
|
132
|
-
if (!this.
|
|
133
|
-
this.
|
|
166
|
+
private tick() {
|
|
167
|
+
if (!this._running) {
|
|
168
|
+
this._pendingTick = false
|
|
134
169
|
return
|
|
135
170
|
}
|
|
136
171
|
|
|
172
|
+
// Check for expired items
|
|
173
|
+
this.checkExpiredItems()
|
|
174
|
+
|
|
137
175
|
while (
|
|
138
|
-
this.
|
|
139
|
-
!this.
|
|
176
|
+
this._activeItems.size < this._options.concurrency &&
|
|
177
|
+
!this.getIsEmpty()
|
|
140
178
|
) {
|
|
141
179
|
const nextFn = this.getNextItem()
|
|
142
180
|
if (!nextFn) {
|
|
143
181
|
break
|
|
144
182
|
}
|
|
145
|
-
|
|
146
|
-
this.
|
|
183
|
+
this._activeItems.add(nextFn)
|
|
184
|
+
this._options.onItemsChange(this)
|
|
147
185
|
;(async () => {
|
|
148
186
|
let success = false
|
|
149
187
|
let res!: TValue
|
|
@@ -155,19 +193,19 @@ export class AsyncQueuer<TValue> {
|
|
|
155
193
|
} catch (e) {
|
|
156
194
|
error = e as Error
|
|
157
195
|
} finally {
|
|
158
|
-
this.
|
|
196
|
+
this._activeItems.delete(nextFn)
|
|
197
|
+
this._options.onItemsChange(this)
|
|
159
198
|
}
|
|
160
199
|
|
|
161
|
-
this.activeItems.delete(nextFn)
|
|
162
200
|
if (success) {
|
|
163
|
-
this.
|
|
201
|
+
this._onSuccessCallbacks.forEach((cb) => cb(res))
|
|
164
202
|
} else {
|
|
165
|
-
this.
|
|
203
|
+
this._onErrorCallbacks.forEach((cb) => cb(error!))
|
|
166
204
|
}
|
|
167
|
-
this.
|
|
205
|
+
this._onSettledCallbacks.forEach((cb) => cb(success ? res : error!))
|
|
168
206
|
|
|
169
|
-
if (this.
|
|
170
|
-
setTimeout(() => this.tick(), this.
|
|
207
|
+
if (this._options.wait > 0) {
|
|
208
|
+
setTimeout(() => this.tick(), this._options.wait)
|
|
171
209
|
return
|
|
172
210
|
}
|
|
173
211
|
|
|
@@ -175,7 +213,109 @@ export class AsyncQueuer<TValue> {
|
|
|
175
213
|
})()
|
|
176
214
|
}
|
|
177
215
|
|
|
178
|
-
this.
|
|
216
|
+
this._pendingTick = false
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Checks for and removes expired items from the queuer
|
|
221
|
+
*/
|
|
222
|
+
private checkExpiredItems() {
|
|
223
|
+
if (
|
|
224
|
+
this._options.expirationDuration === Infinity &&
|
|
225
|
+
this._options.getIsExpired === defaultOptions.getIsExpired
|
|
226
|
+
)
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
const now = Date.now()
|
|
230
|
+
const expiredIndices: Array<number> = []
|
|
231
|
+
|
|
232
|
+
// Find indices of expired items
|
|
233
|
+
for (let i = 0; i < this._items.length; i++) {
|
|
234
|
+
const timestamp = this._itemTimestamps[i]
|
|
235
|
+
if (timestamp === undefined) continue
|
|
236
|
+
|
|
237
|
+
const item = this._items[i]
|
|
238
|
+
if (item === undefined) continue
|
|
239
|
+
|
|
240
|
+
const isExpired =
|
|
241
|
+
this._options.getIsExpired !== defaultOptions.getIsExpired
|
|
242
|
+
? this._options.getIsExpired(item, timestamp)
|
|
243
|
+
: now - timestamp > this._options.expirationDuration
|
|
244
|
+
|
|
245
|
+
if (isExpired) {
|
|
246
|
+
expiredIndices.push(i)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Remove expired items from back to front to maintain indices
|
|
251
|
+
for (let i = expiredIndices.length - 1; i >= 0; i--) {
|
|
252
|
+
const index = expiredIndices[i]
|
|
253
|
+
if (index === undefined) continue
|
|
254
|
+
|
|
255
|
+
const expiredItem = this._items[index]
|
|
256
|
+
if (expiredItem === undefined) continue
|
|
257
|
+
|
|
258
|
+
this._items.splice(index, 1)
|
|
259
|
+
this._itemTimestamps.splice(index, 1)
|
|
260
|
+
this._expirationCount++
|
|
261
|
+
this._options.onExpire(expiredItem, this)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (expiredIndices.length > 0) {
|
|
265
|
+
this._options.onItemsChange(this)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Starts the queuer and processes items
|
|
271
|
+
*/
|
|
272
|
+
start(): Promise<void> {
|
|
273
|
+
this._running = true
|
|
274
|
+
if (!this._pendingTick && !this.getIsEmpty()) {
|
|
275
|
+
this._pendingTick = true
|
|
276
|
+
this.tick()
|
|
277
|
+
}
|
|
278
|
+
this._options.onIsRunningChange(this)
|
|
279
|
+
|
|
280
|
+
return new Promise<void>((resolve) => {
|
|
281
|
+
const checkIdle = () => {
|
|
282
|
+
if (this.getIsIdle()) {
|
|
283
|
+
resolve()
|
|
284
|
+
} else {
|
|
285
|
+
setTimeout(checkIdle, 100)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
checkIdle()
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Stops the queuer from processing items
|
|
294
|
+
*/
|
|
295
|
+
stop(): void {
|
|
296
|
+
this._running = false
|
|
297
|
+
this._pendingTick = false
|
|
298
|
+
this._options.onIsRunningChange(this)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Removes all items from the queuer
|
|
303
|
+
*/
|
|
304
|
+
clear(): void {
|
|
305
|
+
this._items = []
|
|
306
|
+
this._options.onItemsChange(this)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Resets the queuer to its initial state
|
|
311
|
+
*/
|
|
312
|
+
reset(withInitialItems?: boolean): void {
|
|
313
|
+
this.clear()
|
|
314
|
+
this._executionCount = 0
|
|
315
|
+
if (withInitialItems) {
|
|
316
|
+
this._items = [...this._options.initialItems]
|
|
317
|
+
}
|
|
318
|
+
this._running = this._options.started
|
|
179
319
|
}
|
|
180
320
|
|
|
181
321
|
/**
|
|
@@ -183,10 +323,12 @@ export class AsyncQueuer<TValue> {
|
|
|
183
323
|
*/
|
|
184
324
|
addItem(
|
|
185
325
|
fn: (() => Promise<TValue>) & { priority?: number },
|
|
186
|
-
position: QueuePosition = this.
|
|
326
|
+
position: QueuePosition = this._options.addItemsTo,
|
|
187
327
|
runOnUpdate: boolean = true,
|
|
188
328
|
): Promise<TValue> {
|
|
189
|
-
if (this.
|
|
329
|
+
if (this.getIsFull()) {
|
|
330
|
+
this._rejectionCount++
|
|
331
|
+
this._options.onReject(fn, this)
|
|
190
332
|
return Promise.reject(new Error('Queuer is full'))
|
|
191
333
|
}
|
|
192
334
|
|
|
@@ -207,40 +349,44 @@ export class AsyncQueuer<TValue> {
|
|
|
207
349
|
|
|
208
350
|
// Get priority either from the function or from getPriority option
|
|
209
351
|
const priority =
|
|
210
|
-
this.
|
|
211
|
-
? this.
|
|
352
|
+
this._options.getPriority !== defaultOptions.getPriority
|
|
353
|
+
? this._options.getPriority(task)
|
|
212
354
|
: task.priority
|
|
213
355
|
|
|
214
356
|
if (priority !== undefined) {
|
|
215
357
|
// Insert based on priority
|
|
216
|
-
const insertIndex = this.
|
|
358
|
+
const insertIndex = this._items.findIndex((existing) => {
|
|
217
359
|
const existingPriority =
|
|
218
|
-
this.
|
|
219
|
-
? this.
|
|
360
|
+
this._options.getPriority !== defaultOptions.getPriority
|
|
361
|
+
? this._options.getPriority(existing)
|
|
220
362
|
: (existing as any).priority
|
|
221
363
|
return existingPriority > priority
|
|
222
364
|
})
|
|
223
365
|
|
|
224
366
|
if (insertIndex === -1) {
|
|
225
|
-
this.
|
|
367
|
+
this._items.push(task)
|
|
368
|
+
this._itemTimestamps.push(Date.now())
|
|
226
369
|
} else {
|
|
227
|
-
this.
|
|
370
|
+
this._items.splice(insertIndex, 0, task)
|
|
371
|
+
this._itemTimestamps.splice(insertIndex, 0, Date.now())
|
|
228
372
|
}
|
|
229
373
|
} else {
|
|
230
374
|
// Default FIFO/LIFO behavior
|
|
231
375
|
if (position === 'front') {
|
|
232
|
-
this.
|
|
376
|
+
this._items.unshift(task)
|
|
377
|
+
this._itemTimestamps.unshift(Date.now())
|
|
233
378
|
} else {
|
|
234
|
-
this.
|
|
379
|
+
this._items.push(task)
|
|
380
|
+
this._itemTimestamps.push(Date.now())
|
|
235
381
|
}
|
|
236
382
|
}
|
|
237
383
|
|
|
238
384
|
if (runOnUpdate) {
|
|
239
|
-
this.
|
|
385
|
+
this._options.onItemsChange(this)
|
|
240
386
|
}
|
|
241
387
|
|
|
242
|
-
if (this.
|
|
243
|
-
this.
|
|
388
|
+
if (this._running && !this._pendingTick) {
|
|
389
|
+
this._pendingTick = true
|
|
244
390
|
this.tick()
|
|
245
391
|
}
|
|
246
392
|
})
|
|
@@ -250,20 +396,22 @@ export class AsyncQueuer<TValue> {
|
|
|
250
396
|
* Removes and returns an item from the queuer
|
|
251
397
|
*/
|
|
252
398
|
getNextItem(
|
|
253
|
-
position: QueuePosition = this.
|
|
399
|
+
position: QueuePosition = this._options.getItemsFrom,
|
|
254
400
|
): (() => Promise<TValue>) | undefined {
|
|
255
401
|
let item: (() => Promise<TValue>) | undefined
|
|
256
402
|
|
|
257
403
|
if (position === 'front') {
|
|
258
|
-
item = this.
|
|
404
|
+
item = this._items.shift()
|
|
405
|
+
this._itemTimestamps.shift()
|
|
259
406
|
} else {
|
|
260
|
-
item = this.
|
|
407
|
+
item = this._items.pop()
|
|
408
|
+
this._itemTimestamps.pop()
|
|
261
409
|
}
|
|
262
410
|
|
|
263
411
|
if (item !== undefined) {
|
|
264
|
-
this.
|
|
265
|
-
this.
|
|
266
|
-
this.
|
|
412
|
+
this._executionCount++
|
|
413
|
+
this._options.onItemsChange(this)
|
|
414
|
+
this._options.onGetNextItem(item, this)
|
|
267
415
|
}
|
|
268
416
|
return item
|
|
269
417
|
}
|
|
@@ -271,89 +419,94 @@ export class AsyncQueuer<TValue> {
|
|
|
271
419
|
/**
|
|
272
420
|
* Returns an item without removing it
|
|
273
421
|
*/
|
|
274
|
-
|
|
422
|
+
getPeek(
|
|
423
|
+
position: QueuePosition = 'front',
|
|
424
|
+
): (() => Promise<TValue>) | undefined {
|
|
275
425
|
if (position === 'front') {
|
|
276
|
-
return this.
|
|
426
|
+
return this._items[0]
|
|
277
427
|
}
|
|
278
|
-
return this.
|
|
428
|
+
return this._items[this._items.length - 1]
|
|
279
429
|
}
|
|
280
430
|
|
|
281
431
|
/**
|
|
282
432
|
* Returns true if the queuer is empty
|
|
283
433
|
*/
|
|
284
|
-
|
|
285
|
-
return this.
|
|
434
|
+
getIsEmpty(): boolean {
|
|
435
|
+
return this._items.length === 0
|
|
286
436
|
}
|
|
287
437
|
|
|
288
438
|
/**
|
|
289
439
|
* Returns true if the queuer is full
|
|
290
440
|
*/
|
|
291
|
-
|
|
292
|
-
return this.
|
|
441
|
+
getIsFull(): boolean {
|
|
442
|
+
return this._items.length >= this._options.maxSize
|
|
293
443
|
}
|
|
294
444
|
|
|
295
445
|
/**
|
|
296
446
|
* Returns the current size of the queuer
|
|
297
447
|
*/
|
|
298
|
-
|
|
299
|
-
return this.
|
|
448
|
+
getSize(): number {
|
|
449
|
+
return this._items.length
|
|
300
450
|
}
|
|
301
451
|
|
|
302
452
|
/**
|
|
303
|
-
*
|
|
453
|
+
* Returns a copy of all items in the queuer
|
|
304
454
|
*/
|
|
305
|
-
|
|
306
|
-
this.
|
|
307
|
-
this.options.onUpdate(this)
|
|
455
|
+
getAllItems(): Array<() => Promise<TValue>> {
|
|
456
|
+
return [...this.getActiveItems(), ...this.getPendingItems()]
|
|
308
457
|
}
|
|
309
458
|
|
|
310
459
|
/**
|
|
311
|
-
*
|
|
460
|
+
* Returns the active items
|
|
312
461
|
*/
|
|
313
|
-
|
|
314
|
-
this.
|
|
315
|
-
this.executionCount = 0
|
|
316
|
-
if (withInitialItems) {
|
|
317
|
-
this.items = [...this.options.initialItems]
|
|
318
|
-
}
|
|
319
|
-
this.running = this.options.started
|
|
462
|
+
getActiveItems(): Array<() => Promise<TValue>> {
|
|
463
|
+
return Array.from(this._activeItems)
|
|
320
464
|
}
|
|
321
465
|
|
|
322
466
|
/**
|
|
323
|
-
* Returns
|
|
467
|
+
* Returns the pending items
|
|
324
468
|
*/
|
|
325
|
-
|
|
326
|
-
return [...this.
|
|
469
|
+
getPendingItems(): Array<() => Promise<TValue>> {
|
|
470
|
+
return [...this._items]
|
|
327
471
|
}
|
|
328
472
|
|
|
329
473
|
/**
|
|
330
474
|
* Returns the number of items that have been removed from the queuer
|
|
331
475
|
*/
|
|
332
476
|
getExecutionCount(): number {
|
|
333
|
-
return this.
|
|
477
|
+
return this._executionCount
|
|
334
478
|
}
|
|
335
479
|
|
|
336
480
|
/**
|
|
337
|
-
* Returns the
|
|
481
|
+
* Returns the number of items that have been rejected from the queuer
|
|
338
482
|
*/
|
|
339
|
-
|
|
340
|
-
return
|
|
483
|
+
getRejectionCount(): number {
|
|
484
|
+
return this._rejectionCount
|
|
341
485
|
}
|
|
342
486
|
|
|
343
487
|
/**
|
|
344
|
-
* Returns the
|
|
488
|
+
* Returns true if the queuer is running
|
|
345
489
|
*/
|
|
346
|
-
|
|
347
|
-
return this.
|
|
490
|
+
getIsRunning(): boolean {
|
|
491
|
+
return this._running
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Returns true if the queuer is running but has no items to process
|
|
496
|
+
*/
|
|
497
|
+
getIsIdle(): boolean {
|
|
498
|
+
return this._running && this.getIsEmpty() && this._activeItems.size === 0
|
|
348
499
|
}
|
|
349
500
|
|
|
350
501
|
/**
|
|
351
502
|
* Adds a callback to be called when a task succeeds
|
|
352
503
|
*/
|
|
353
504
|
onSuccess(cb: (result: TValue) => void) {
|
|
354
|
-
this.
|
|
505
|
+
this._onSuccessCallbacks.push(cb)
|
|
355
506
|
return () => {
|
|
356
|
-
this.
|
|
507
|
+
this._onSuccessCallbacks = this._onSuccessCallbacks.filter(
|
|
508
|
+
(d) => d !== cb,
|
|
509
|
+
)
|
|
357
510
|
}
|
|
358
511
|
}
|
|
359
512
|
|
|
@@ -361,9 +514,9 @@ export class AsyncQueuer<TValue> {
|
|
|
361
514
|
* Adds a callback to be called when a task errors
|
|
362
515
|
*/
|
|
363
516
|
onError(cb: (error: Error) => void) {
|
|
364
|
-
this.
|
|
517
|
+
this._onErrorCallbacks.push(cb)
|
|
365
518
|
return () => {
|
|
366
|
-
this.
|
|
519
|
+
this._onErrorCallbacks = this._onErrorCallbacks.filter((d) => d !== cb)
|
|
367
520
|
}
|
|
368
521
|
}
|
|
369
522
|
|
|
@@ -371,56 +524,19 @@ export class AsyncQueuer<TValue> {
|
|
|
371
524
|
* Adds a callback to be called when a task is settled
|
|
372
525
|
*/
|
|
373
526
|
onSettled(cb: (result: TValue | Error) => void) {
|
|
374
|
-
this.
|
|
527
|
+
this._onSettledCallbacks.push(cb)
|
|
375
528
|
return () => {
|
|
376
|
-
this.
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Starts the queuer and processes items
|
|
382
|
-
*/
|
|
383
|
-
start(): Promise<void> {
|
|
384
|
-
this.running = true
|
|
385
|
-
if (!this.pendingTick && !this.isEmpty()) {
|
|
386
|
-
this.pendingTick = true
|
|
387
|
-
this.tick()
|
|
529
|
+
this._onSettledCallbacks = this._onSettledCallbacks.filter(
|
|
530
|
+
(d) => d !== cb,
|
|
531
|
+
)
|
|
388
532
|
}
|
|
389
|
-
this.options.onUpdate(this)
|
|
390
|
-
|
|
391
|
-
return new Promise<void>((resolve) => {
|
|
392
|
-
const checkIdle = () => {
|
|
393
|
-
if (this.isIdle()) {
|
|
394
|
-
resolve()
|
|
395
|
-
} else {
|
|
396
|
-
setTimeout(checkIdle, 100)
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
checkIdle()
|
|
400
|
-
})
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Stops the queuer from processing items
|
|
405
|
-
*/
|
|
406
|
-
stop(): void {
|
|
407
|
-
this.running = false
|
|
408
|
-
this.pendingTick = false
|
|
409
|
-
this.options.onUpdate(this)
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Returns true if the queuer is running
|
|
414
|
-
*/
|
|
415
|
-
isRunning(): boolean {
|
|
416
|
-
return this.running
|
|
417
533
|
}
|
|
418
534
|
|
|
419
535
|
/**
|
|
420
|
-
* Returns
|
|
536
|
+
* Returns the number of items that have expired from the queuer
|
|
421
537
|
*/
|
|
422
|
-
|
|
423
|
-
return this.
|
|
538
|
+
getExpirationCount(): number {
|
|
539
|
+
return this._expirationCount
|
|
424
540
|
}
|
|
425
541
|
}
|
|
426
542
|
|
|
@@ -441,7 +557,9 @@ export class AsyncQueuer<TValue> {
|
|
|
441
557
|
* @param options - Configuration options for the AsyncQueuer
|
|
442
558
|
* @returns A bound addItem function that can be used to add tasks to the queuer
|
|
443
559
|
*/
|
|
444
|
-
export function asyncQueue<TValue>(
|
|
445
|
-
|
|
560
|
+
export function asyncQueue<TValue>(
|
|
561
|
+
options: Omit<AsyncQueuerOptions<TValue>, 'started'> = {},
|
|
562
|
+
) {
|
|
563
|
+
const queuer = new AsyncQueuer<TValue>(options)
|
|
446
564
|
return queuer.addItem.bind(queuer)
|
|
447
565
|
}
|