neverthrow icon indicating copy to clipboard operation
neverthrow copied to clipboard

[Feature Request] Sequential ResultAsync combine

Open sebiglesias opened this issue 1 year ago • 1 comments

One of our use cases of ResultAsync.combine is to act as a Promise.all for several external API calls, which is great for combining multiple ResultAsyncs running in parallel.

The array of these () => ResultAsync<T,E> can become quite large, so for some scenarios a sequential approach proved to solve some rate-limiting issues.

I would imagine there are other scenarios where sequentially running an array of functions returning ResultAsyncs could be useful, such as:

  • Having certain dependencies between each task/function, or where the outcome of one affects the other.
  • A primitive way of Rate Limiting/Throttling
  • In non idempotent operations, order of each task is important and running them sequentially would be a must. (Specially on longer chains)
  • Batch processing

It can also be used as a way to prevent error propagation, if none can fail, as soon as one does no additional calls are made.

The code for this could resemble something like:

static combineSequential<T, E>(
  promFuncs: Array<() => ResultAsync<T, E>>
): ResultAsync<T[], E> {
  return promFuncs.reduce(
    (p, c) =>
      p.andThen((acc) => {
        return c().map((v) => {
          acc.push(v);
          return acc;
        });
      }),
    okAsync<T[], E>([] as T[])
  );
}

@pyrho came up with this.

sebiglesias avatar Oct 09 '24 19:10 sebiglesias

ha! great minds think alike. I wrote this a few days ago:


/**
 * Run a list of functions sequentially and return immediately when the
 * first error is generated.
 * If no errors are found, return a list of result values in the same order.
 */
export function runInSequence<T, E>(
    fns: ReadonlyArray<() => ResultAsync<T, E>>,
): ResultAsync<Array<T>, E> {
    const run = async () => {
        const okValues: Array<T> = [];

        for (const fn of fns) {
            const result = await fn();
            if (result.isErr()) {
                return err(result.error);
            }
            okValues.push(result.value);
        }

        return ok(okValues);
    };

    return new ResultAsync(run());
}

macksal avatar Oct 18 '24 07:10 macksal