以下函数应该创建多个 Promise 并批量抵消这些 Promise,以便第一批始终在第二批之前解决,依此类推。
它将 Promise 作为平面数组返回,以便调用者可以在评估时获取每个 Promise 的值,而不是等到最后一次获取所有值。
下面的函数可以很好地做到这一点,除了无论我在哪里调用它(以及使用任何参数),返回值总是自动键入为
Promise<unknown>[]
。 IE。它总是自动将 R 设置为未知。
/**
* This function will take an array of functions that return
* promises and execute them in batches of a given size. Instead of
* returning the promises combined into a single promise, it will
* return each promise individually as a flat array of promises but
* will ensure that the promises are executed in batches of 'batchSize'.
* This is useful when you want promises to run in batches but also
* want to be able to access the results of each promise as its resolves.
* Failure of any promise will not stop the execution of the other promises.
* @param funcs the async functions to execute
* @param batchSize the size of each batch
* @returns an array of promises that will resolve in batches
*/
export function runPromisesInBatches<R, F extends () => Promise<R>>(
funcs: Array<F>,
batchSize: number,
): Array<Promise<R>> {
/**
* The current batch of promises being executed.
* This will be reset when the batch size is reached.
*/
let currentBatch: Promise<R>[] = [];
/**
* The list of batches that have been executed.
*/
const batches: Promise<PromiseSettledResult<Awaited<R>>[]>[] = [];
const wrappedPromises = funcs.map((f, i) => {
/**
* If the batch size has been reached, create a new batch
* and push the current batch into the list of batches.
* This will allow the promises to be executed
* in batches of 'batchSize'.
*/
if (i % batchSize === 0 && currentBatch.length) {
const b = Promise.allSettled([...currentBatch]);
batches.push(b);
currentBatch = [];
}
/**
* Delay the execution of the promise until the last batch has resolved
* if that is a last batch (eg. the first batch will not wait for anything).
*/
const lastBatch = batches.at(-1);
let promise: Promise<R>;
console.log("BATCH", !!lastBatch);
if (lastBatch) {
promise = lastBatch.then(() => f()).catch(() => f());
} else {
promise = f();
}
currentBatch.push(promise);
return promise;
});
return wrappedPromises;
}
// Example usage:
(async () => {
const tasks : (() => Promise<string>)[] = [
() => new Promise<string>((resolve) => setTimeout(() => resolve('Task 1'), 3000)),
() => new Promise<string>((_, reject) => setTimeout(() => reject('Task 3 Error'), 8000)),
() => new Promise<string>((resolve) => setTimeout(() => resolve('Task 2'), 2000)),
() => new Promise<string>((resolve) => setTimeout(() => resolve('Task 4'), 1000)),
];
try {
// XXX: results should be an array of Promise<string> but, instead, Promise<unknown>
const results = runPromisesInBatches(tasks, 2)
results.forEach(r => {
r.then(d => console.log(d))
})
console.log('Results:', results);
} catch (error) {
console.error('Error in batch processing:', error);
}
})();
我做错了什么?为什么 Typescript 无法推断类型?
泛型约束不是其他泛型类型参数的推理站点。 因此,如果您有一个像
function f<T, U extends F<T>>(u: U): void
这样的函数签名,并且调用 f(u)
,则无法从中推断出 T
。 TypeScript 会推断 U
是 u
的类型,但它不会决定 T
应该是使 U extends F<T>
成立的任何内容。 microsoft/TypeScript#7234 上有一个旧建议,对推理站点进行约束,但它被拒绝,转而支持支持 intersections。因此,您需要的不是 function f<T, U extends F<T>>(u: U): void
,而是类似 function f<T, U>(u: F<T> & U): void
的内容,然后 f(u)
可能会推断出 U
(直接)和 T
(间接)。 当然,如果您只是因为认为有必要推断 U
而尝试推断 T
,那么您不妨完全忽略它并写下 function f<T>(u: F<T>): void
。
因此,对于您的示例代码,我会完全删除
F
并仅使用 () => Promise<R>
代替:
declare function runPromisesInBatches<R>(
funcs: Array<() => Promise<R>>,
batchSize: number,
): Array<Promise<R>>;
然后它应该按预期工作:
const results = runPromisesInBatches(tasks, 2);
// const results: Promise<string>[]
这里
R
已根据需要推断为 string
。