Home > Software engineering >  Recursive serial call promise
Recursive serial call promise

Time:10-01

I want to implement a recursive serial call to the promise method times, returning the result of the fn function of calling N times to the array.

At present, I added an additional attribute results to the times function to save the results of each fn call.

I also don’t want to use module-scoped variables to save the results. Or, save the results by passing additional parameters like times(fn, n, results), it will break the function signature.

async/await syntax is not allowed.

Is there any way to only use function local variables to save the result?

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[] },
);

Usage:

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' ]
}

CodePudding user response:

There's no reason to use a stateful result variable at all. Just invert the recursion direction:

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;
    })
  });
}

But if you insist on creating promises more gradually, you'll need to use an accumulator parameter:

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);
  });
}

If you don't like having that additional optional parameter in your signature, use a local helper function that does the recursion:

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();
}

CodePudding user response:

What you are trying to achieve is to have a sequential and a recursive pattern. You can achieve it as following

Solution:

  • In your function create 2 promises
    • finalPromise: Promise to be returned from function
    • promise: Promise that will be created by passed fn
  • For finalPromise, copy reference of resolver function and store it for manual call, say resolverFn
  • Create a variable result to store values. This should be of type any[]
  • On promise.then,
    • Push received value in result
    • Make subsequent call to times.
  • Trick for subsequent calls,
    • Callback passed to times will be resolverFn if n === 1. Make sure to bind result before you pass or do () => resolverFn(result)
    • Else pass fn
  • Store output of this call, say innerPromise and on it's then, call resolverFn with result
  • Related