函数的返回类型始终为“Promise<unknown>”;没有正确推断

问题描述 投票:0回答:1

以下函数应该创建多个 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 无法推断类型?

javascript typescript promise typing
1个回答
0
投票

泛型约束不是其他泛型类型参数的推理站点。 因此,如果您有一个像

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

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.