typescript-result icon indicating copy to clipboard operation
typescript-result copied to clipboard

Feature Request: Duality of `Result.all`

Open ForkKILLET opened this issue 3 months ago • 1 comments

Why not add Result.any as the duality of Result.all, like Promise.all v.s. Promise.any and Array#every v.s. Array#some?

ForkKILLET avatar Aug 09 '25 20:08 ForkKILLET

Interesting suggestion, let me break it down and get a feel for how this would look/behave:

declare a: Result<"a", ErrorA>;
declare b: Result<"b", ErrorB>;
declare c: Result<"c", ErrorC>;
  • Result.all - given one or multiple inputs, it returns a Result or AsyncResult with a list of resolved encapsulated values on success, or the first error in case one of the inputs resolves to a failure.

    Result.all(a, b, c); // Result<["a", "b", "c"], ErrorA | ErrorB | ErrorC>
    
  • Result.any - given one or multiple inputs, it returns a Result or AsyncResult with the value of the first successful item it encounters, or a list of all errors in case all items turn out to be a failure. The inverse of Result.all.

    Result.any(a, b, c); // Result<"a" | "b" | "c", [ErrorA, ErrorB, ErrorC]>
    
  • Result.allSettled - given one or multiple inputs, it returns an array (or promise with an array if one of the provided items is async) of results that correspond to the provided input.

    Result.allSettled(a, b, c); // [Result<"a", ErrorA> , Result<"b", ErrorB>, Result<"c", ErrorC>]
    

    For sync input, this feels redundant tbh, but for async input it could make sense: execute multiple async operations in parallel, and do something with the settled outcome of all operations, whether that is success or failure. E.g.

    declare function asyncOperationA(): AsyncResult<"a", ErrorA>;
    declare function asyncOperationB(): AsyncResult<"b", ErrorB>;
    declare function asyncOperationC(): AsyncResult<"c", ErrorC>;
    
    const results = await Result.allSettled(
      asyncOperationA(),
      asyncOperationB(),
      asyncOperationC()
    ); // [Result<"a", ErrorA>, Result<"b", ErrorB>, Result<"c", ErrorC>]
    
    for (const result of results) {
      if (!result.ok) {
        // Log, report, add to DLQ, etc...
      }
    } 
    
  • Result.race - also only makes sense for async operations -> given one or multiple inputs, it returns an AsyncResult with the first item that settles (success or failure).

    Result.race(a, b, c); // AsyncResult<"a", ErrorA> | AsyncResult<"b", ErrorB> | AsyncResult<"c", ErrorC>
    

I feel like Result.any seems to be the most straightforward with regards to the expectations / compared to Promise.any. With Result.allSettled and Result.race, I need to think about it some more, and some concrete use-cases would help as well to make sure that multiple angles and edge cases have been at least considered.

I guess it makes sense to start with Result.any and see where it goes from there. I'll let you know if I have any updates.

everweij avatar Aug 10 '25 19:08 everweij