ai-tube / src /lib /utils /waitPromisesUntil.ts
jbilcke-hf's picture
jbilcke-hf HF staff
Modifying AiTube to support Stories Factory use cases
6215321
raw
history blame
4.11 kB
export enum PromiseResponseType {
Resolved,
Rejected,
Expired
}
export type PromiseResponse<T> =
{
status: PromiseResponseType.Resolved
value: T
error: undefined
} |
{
status: PromiseResponseType.Rejected
value: undefined
error: Error
} |
{
status: PromiseResponseType.Expired
value: undefined
error: Error
}
/**
* This function waits for an array of promises to finish
*
* Behavior:
*
* As soon as any of the promises resolve,
* this triggers an internal countdown (based on maxRunTimeAfterFirstResolveInMs)
* after which the function will stop everything, and return the promises results.
* Promises that finish after countdownAfterFirstResolveInMs will be ignored and their result discarded.
*
* The result is an array matching the input number of promises, but with different status based on what happened:
*
* Promises that resolved will be of type `{ status: PromiseResponseType.Resolved, value: ...... }`.
* Promises that failed will be of type `{ status: PromiseResponseType.Rejected, error: ...... }`.
* Promises that failed will be of type `{ status: PromiseResponseType.Expired, error: ...... }`.
*
* Note that it is possible that no promises resolves at all,
* which is why we also have a global expiration timeout (based on maxTotalRunTimeInMs) with more priority,
* that will also stop the function and return the promises results (they will also have the expired status)
*
* Note that maxTotalRunTimeInMs is reached, countdownAfterFirstResolveInMs becomes irrelevant,
* so all promises that finish after maxTotalRunTimeInMs will be ignored and their result discarded.
*/
export async function waitPromisesUntil<T>(
promises: Promise<T>[],
countdownAfterFirstResolveInMs: number = 1000,
maxTotalRunTimeInMs: number = 10000
): Promise<PromiseResponse<T>[]> {
// This will store the result for each promise as it finishes or the operation is terminated.
const results: PromiseResponse<T>[] = Array(promises.length).fill(undefined);
/*{
status: "expired",
error: new Error("Promise expired")
});
*/
// Timer to handle maxTotalRunTimeInMs
let globalTimeoutHandler: any;
// Timer to handle countdownAfterFirstResolveInMs
let firstResolveCountdownHandler: any;
// Helper function to handle promise resolution
const handleResolved = (index: number, value: T) => {
if (!results[index]) { // If result isn't yet set
results[index] = { status: PromiseResponseType.Resolved, value, error: undefined };
}
if (!firstResolveCountdownHandler) {
// Start countdown after the first resolve
firstResolveCountdownHandler = setTimeout(() => {
finalizePromises(); // Stop accepting results after the countdown
}, countdownAfterFirstResolveInMs);
}
};
// Helper function to handle promise rejection
const handleRejected = (index: number, error: any) => {
if (!results[index]) {
results[index] = { status: PromiseResponseType.Rejected, error, value: undefined };
}
};
// Early finalization function to handle both timeouts
const finalizePromises = () => {
clearTimeout(globalTimeoutHandler);
clearTimeout(firstResolveCountdownHandler);
for (let i = 0; i < promises.length; i++) {
if (!results[i]) { // Promises that have not been settled by either handler
results[i] = { status: PromiseResponseType.Expired, error: new Error('Promise expired'), value: undefined };
}
}
};
// Wrap each promise with resolution and rejection handlers
const watchedPromises = promises.map((promise, index) =>
promise.then(
value => handleResolved(index, value),
error => handleRejected(index, error)
)
);
// Set the global timeout
globalTimeoutHandler = setTimeout(() => {
finalizePromises();
}, maxTotalRunTimeInMs);
// Await all wrapped promises
await Promise.all(watchedPromises.map(p => p.catch(() => {}))); // Catch errors to prevent early exits
finalizePromises(); // Finalize to ensure all promises not settled are marked expired
return results;
}