-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: make promise throttle level consistent #240
Changes from 1 commit
03e38b4
d60e296
1f40473
edd9c1c
2e88186
1ef30b3
f0506f3
bc79f87
d915e28
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,15 @@ export type PromiseItem<T, O = T | undefined> = { | |
producer: (source: T, throttledPromise: ThrottledPromiseAll<T, O | undefined>) => Promise<O | undefined>; | ||
}; | ||
|
||
type IndexedProducer<T, O = T> = PromiseItem<T, O> & { | ||
index: number; | ||
}; | ||
|
||
type IndexedResult<O> = { | ||
index: number; | ||
result: O | undefined; | ||
}; | ||
|
||
/** | ||
* A promise that throttles the number of promises running at a time. | ||
* | ||
|
@@ -39,7 +48,7 @@ export class ThrottledPromiseAll<T, O = T> { | |
private readonly concurrency: number; | ||
private wait: Duration; | ||
private timeout: NodeJS.Timeout | undefined; | ||
readonly #results: Array<O | undefined> = []; | ||
readonly #results: Array<IndexedResult<O> | undefined> = []; | ||
|
||
/** | ||
* Construct a new ThrottledPromiseAll. | ||
|
@@ -56,7 +65,7 @@ export class ThrottledPromiseAll<T, O = T> { | |
* Returns the results of the promises that have been resolved. | ||
*/ | ||
public get results(): Array<O | undefined> { | ||
return this.#results; | ||
return this.#results.sort((a, b) => (a?.index ?? 0) - (b?.index ?? 0)).map((r) => r?.result); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return the results in same order supplied by user |
||
} | ||
|
||
/** | ||
|
@@ -109,7 +118,7 @@ export class ThrottledPromiseAll<T, O = T> { | |
await this.dequeue(); | ||
} | ||
this.stop(); | ||
return this.#results; | ||
return this.results; | ||
} catch (e) { | ||
this.stop(); | ||
throw e; | ||
|
@@ -124,14 +133,35 @@ export class ThrottledPromiseAll<T, O = T> { | |
} | ||
|
||
private async dequeue(): Promise<void> { | ||
while (this.queue.length > 0) { | ||
const next = this.queue.slice(0, this.concurrency); | ||
this.queue.splice(0, this.concurrency); | ||
// eslint-disable-next-line no-await-in-loop | ||
const results = await Promise.all( | ||
next.map((item) => item.producer(item.source, this).catch((e) => Promise.reject(e))) | ||
const generator = function* ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes code a bit more concise |
||
data: Array<PromiseItem<T, O | undefined>> | ||
): Generator<PromiseItem<T, O | undefined> | undefined> { | ||
while (data.length > 0) { | ||
yield data.shift(); | ||
} | ||
}; | ||
const concurrencyPool: Array<Promise<IndexedResult<O> | undefined>> = []; | ||
const get = generator(this.queue); | ||
let index = 0; | ||
while (this.queue.length > 0 || concurrencyPool.length > 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stay here while there are things to do. |
||
while (concurrencyPool.length < this.concurrency) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make sure there are always running promises at the concurrency level |
||
const item = get.next().value as PromiseItem<T, O | undefined>; | ||
if (!item) { | ||
break; | ||
} | ||
|
||
const p: IndexedProducer<T, O> = { ...item, index: index++ }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add in the index property to the supplied producer |
||
concurrencyPool.push( | ||
p | ||
.producer(item.source, this) | ||
.then((result) => ({ index: p.index, result })) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. capture the index in the result |
||
.catch((e) => Promise.reject(e)) | ||
); | ||
} | ||
this.#results.push( | ||
// eslint-disable-next-line no-await-in-loop | ||
await Promise.race([concurrencyPool.shift(), ...concurrencyPool]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Race until one finishes. |
||
); | ||
this.#results.push(...results); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Internal use type to add index to input and output.
Index is used to maintain result order to align results with Promise.all