supermacro / neverthrow

Type-Safe Errors for JS & TypeScript
MIT License
4.03k stars 84 forks source link

[Feature Request] Sequential ResultAsync combine #592

Open sebiglesias opened 1 month ago

sebiglesias commented 1 month ago

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:

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.

macksal commented 1 month ago

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