递归串行调用承诺

Recursive serial call promise

我想实现对promise方法times的递归串行调用,将fn函数调用N次的结果返回给数组

目前我在times函数中添加了一个额外的属性results来保存每次fn调用的结果

我也不想使用模块范围的变量来保存结果。或者,通过传递附加参数(如 times(fn, n, results))来保存结果,这将破坏函数签名。

不允许

async/await 语法。

有没有办法只用函数局部变量来保存结果?

const times = Object.assign(
  (fn: (...args: any) => Promise<any>, n: number = 1) => {
    if (n === 0) return times.results;
    return fn().then((res) => {
      times.results.push(res);
      return times(fn, --n);
    });
  },
  { results: [] as any[] },
);

用法:

const createPromise = (args: any) =>
  new Promise((resolve) => {
    setTimeout(() => {
      console.log(`[${new Date().toISOString()}]args: `, args);
      resolve(args);
    }, 1000);
  });

async function test() {
  const actual = await times(() => asyncFn('data'), 3);
  console.log(actual);  // output: [ 'data', 'data', 'data' ]
}

您想要实现的是具有顺序和递归模式。您可以通过以下方式实现它

Solution:

  • 在你的函数中创建 2 个承诺
    • finalPromise: 从函数返回的承诺
    • promise:将由传递的 fn
    • 创建的 Promise
  • 对于finalPromise,复制解析器函数的引用并存储它以供手动调用,比如resolverFn
  • 创建一个变量result 来存储值。这应该是 any[]
  • 类型
  • promise.then
    • 将收到的值推送到 result
    • 对时间进行后续调用。
  • 后续调用的技巧,
    • 如果 n === 1,传递给 times 的回调将是 resolverFn。确保在通过或执行 () => resolverFn(result)
    • 之前绑定 result
    • 否则通过fn
  • 存储此调用的输出,比如 innerPromise 然后在其上,用 result
  • 调用 resolverFn

根本没有理由使用有状态 result 变量。只需反转递归方向:

function times<T>(fn: () => Promise<T>, n: number = 1): Promise<T[]> {
  if (n === 0) return Promise.resolve([]);
  else return times(fn, n-1).then(results =>
    fn().then(res => {
      results.push(res);
      return results;
    })
  });
}

但是如果你坚持更渐进地创建承诺,你将需要使用累加器参数:

function times<T>(fn: () => Promise<T>, n: number = 1, results: T[] = []): Promise<T[]> {
  if (n === 0) return Promise.resolve(results);
  else return fn().then(res => {
    results.push(res);
    return times(fn, n-1, results);
  });
}

如果您不喜欢在签名中包含额外的可选参数,请使用执行递归的本地辅助函数:

function times<T>(fn: () => Promise<T>, n: number = 1): Promise<T[]> {
  function recurse(n: number, results: T[]): Promise<T[]> {
    if (n === 0) return Promise.resolve(results);
    else return fn().then(res => {
      results.push(res);
      return recurse(n-1, results);
    });
  }
  return recurse(n, []);
}
// or more ugly (stateful):
function times<T>(fn: () => Promise<T>, n: number = 1): Promise<T[]> {
  let results: T[] = [];
  function recurse(): Promise<T[]> {
    if (n-- === 0) return Promise.resolve(results);
    else return fn().then(res => {
      results.push(res);
      return recurse();
    });
  }
  return recurse();
}