Skip to content

feat: add partition static function #526

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ For asynchronous tasks, `neverthrow` offers a `ResultAsync` class which wraps a
- [`Result.combine` (static class method)](#resultcombine-static-class-method)
- [`Result.combineWithAllErrors` (static class method)](#resultcombinewithallerrors-static-class-method)
- [`Result.safeUnwrap()`](#resultsafeunwrap)
- [`Result.partition (static class method)`](#resultpartition-static-class-method)
+ [Asynchronous API (`ResultAsync`)](#asynchronous-api-resultasync)
- [`okAsync`](#okasync)
- [`errAsync`](#errasync)
Expand All @@ -53,6 +54,7 @@ For asynchronous tasks, `neverthrow` offers a `ResultAsync` class which wraps a
- [`ResultAsync.combine` (static class method)](#resultasynccombine-static-class-method)
- [`ResultAsync.combineWithAllErrors` (static class method)](#resultasynccombinewithallerrors-static-class-method)
- [`ResultAsync.safeUnwrap()`](#resultasyncsafeunwrap)
- [`ResultAsync.partition (static class method)`](#resultasyncpartition-static-class-method)
+ [Utilities](#utilities)
- [`fromThrowable`](#fromthrowable)
- [`fromPromise`](#frompromise)
Expand Down Expand Up @@ -669,6 +671,49 @@ const result = Result.combineWithAllErrors(resultList)
Allows for unwrapping a `Result` or returning an `Err` implicitly, thereby reducing boilerplate.


[⬆️ Back to top](#toc)

---

#### `Result.partition` (static class method)

> Although Result is not an actual JS class, the way that `partition` has been implemented requires that you call `partition` as though it were a static method on `Result`. See examples below.

Partition lists of `Result`s.

**`partition` works on both heterogeneous and homogeneous lists**. This means that you can have lists that contain different kinds of `Result`s and still be able to partition them. Note that you cannot partition lists that contain both `Result`s **and** `ResultAsync`s.

The `partition` function takes a list of results and returns a tuple of all `Ok` results and `Err` errors.

Function signature:

```typescript
// homogeneous lists
function partition<T, E>(resultList: Result<T, E>[]): [T[], E[]]

// heterogeneous lists
function partition<T1, T2, E1, E2>(resultList: [ Result<T1, E1>, Result<T2, E2> ]): [(T1 | T2)[], (E1 | E2)[]]
function partition<T1, T2, T3, E1, E2, E3> => [(T1 | T2 | T3) [], (E1 | E2 | E3)[]]
function partition<T1, T2, T3, T4, E1, E2, E3, E4> => [(T1 | T2 | T3 | T4)[], (E1 | E2 | E3 | E4)[]]
// ... etc etc ad infinitum
```

Example usage:

```typescript
const resultList: Result<number, string>[] = [
ok(123),
err('boooom!'),
ok(456),
err('ahhhhh!'),
]

const [results, errors] = Result.partition(resultList)

// results is [123, 456]
// errors is ['boooom!', 'ahhhhh!']
```

[⬆️ Back to top](#toc)

---
Expand Down Expand Up @@ -1148,6 +1193,47 @@ Allows for unwrapping a `Result` or returning an `Err` implicitly, thereby reduc

---

#### `ResultAsync.partition` (static class method)

Partition lists of `ResultAsync`s.

**`partition` works on both heterogeneous and homogeneous lists**. This means that you can have lists that contain different kinds of `ResultAsync`s and still be able to partition them. Note that you cannot partition lists that contain both `Result`s **and** `ResultAsync`s.

The `partition` function takes a list of async results and returns a `Promise` of a tuple of all Ok results and Err errors.

Function signature:

```typescript
// homogeneous lists
function partition<T, E>(resultList: ResultAsync<T, E>[]): Promise<[T[], E[]]>

// heterogeneous lists
function partition<T1, T2, E1, E2>(resultList: [ ResultAsync<T1, E1>, ResultAsync<T2, E2> ]): Promise<[(T1 | T2)[], (E1 | E2)[]]>
function partition<T1, T2, T3, E1, E2, E3> => Promise<[(T1 | T2 | T3)[], (E1 | E2 | E3)[]]>
function partition<T1, T2, T3, T4, E1, E2, E3, E4> => Promise<[(T1 | T2 | T3 | T4)[], (E1 | E2 | E3 | E4)[]]>
// ... etc etc ad infinitum
```

Example usage:

```typescript
const resultList: ResultAsync<number, string>[] = [
okAsync(123),
errAsync('boooom!'),
okAsync(456),
errAsync('ahhhhh!'),
]

const [results, errors] = await ResultAsync.partition(resultList)

// results is [123, 456]
// errors is ['boooom!', 'ahhhhh!']
```

[⬆️ Back to top](#toc)

---

### Utilities

#### `fromThrowable`
Expand Down
11 changes: 11 additions & 0 deletions src/_internals/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,14 @@ export const combineResultAsyncListWithAllErrors = <T, E>(
ResultAsync.fromSafePromise(Promise.all(asyncResultList)).andThen(
combineResultListWithAllErrors,
) as ResultAsync<T[], E[]>

export const partitionResultList = <T, E>(resultList: readonly Result<T, E>[]): [T[], E[]] =>
resultList.reduce(
([oks, errors], result) =>
result.isErr() ? [oks, [...errors, result.error]] : [[...oks, result.value], errors],
[[], []] as [T[], E[]],
)

export const partitionResultAsyncList = <T, E>(
asyncResultList: readonly ResultAsync<T, E>[],
): Promise<[T[], E[]]> => Promise.all(asyncResultList).then(partitionResultList)
17 changes: 17 additions & 0 deletions src/result-async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
InferAsyncOkTypes,
InferErrTypes,
InferOkTypes,
partitionResultAsyncList,
} from './_internals/utils'

export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
Expand Down Expand Up @@ -68,6 +69,18 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
) as CombineResultsWithAllErrorsArrayAsync<T>
}

static partition<
T extends readonly [ResultAsync<unknown, unknown>, ...ResultAsync<unknown, unknown>[]]
>(asyncResultList: T): PartitionResultAsync<T>
static partition<T extends readonly ResultAsync<unknown, unknown>[]>(
asyncResultList: T,
): PartitionResultAsync<T>
static partition<T extends readonly ResultAsync<unknown, unknown>[]>(
Comment on lines +72 to +78
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not following why we need to overload this fn. Please explain.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these overloads have no meaning.

partition will flatten tuple type to array of union by its nature.

Type checking of this test passes even if you remove overloads
https://github.com/supermacro/neverthrow/pull/526/files?w=1#diff-cece62ae24396a5d51e8f2a2cb3abba900652084488023f4de9669eaaf6f81e8R572-R579

asyncResultList: T,
): PartitionResultAsync<T> {
return partitionResultAsyncList(asyncResultList) as PartitionResultAsync<T>
}

map<A>(f: (t: T) => A | Promise<A>): ResultAsync<A, E> {
return new ResultAsync(
this._promise.then(async (res: Result<T, E>) => {
Expand Down Expand Up @@ -176,6 +189,10 @@ export type CombineResultsWithAllErrorsArrayAsync<
? TraverseWithAllErrorsAsync<UnwrapAsync<T>>
: ResultAsync<ExtractOkAsyncTypes<T>, ExtractErrAsyncTypes<T>[number][]>

export type PartitionResultAsync<T extends readonly ResultAsync<unknown, unknown>[]> = Promise<
[ExtractOkAsyncTypes<T>[number][], ExtractErrAsyncTypes<T>[number][]]
>

// Unwraps the inner `Result` from a `ResultAsync` for all elements.
type UnwrapAsync<T> = IsLiteralArray<T> extends 1
? Writable<T> extends [infer H, ...infer Rest]
Expand Down
18 changes: 18 additions & 0 deletions src/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ExtractOkTypes,
InferErrTypes,
InferOkTypes,
partitionResultList,
} from './_internals/utils'

// eslint-disable-next-line @typescript-eslint/no-namespace
Expand Down Expand Up @@ -56,6 +57,18 @@ export namespace Result {
): CombineResultsWithAllErrorsArray<T> {
return combineResultListWithAllErrors(resultList) as CombineResultsWithAllErrorsArray<T>
}

export function partition<
T extends readonly [Result<unknown, unknown>, ...Result<unknown, unknown>[]]
>(resultList: T): PartitionResult<T>
export function partition<T extends readonly Result<unknown, unknown>[]>(
resultList: T,
): PartitionResult<T>
export function partition<T extends readonly Result<unknown, unknown>[]>(
resultList: T,
): PartitionResult<T> {
return partitionResultList(resultList) as PartitionResult<T>
}
}

export type Result<T, E> = Ok<T, E> | Err<T, E>
Expand Down Expand Up @@ -583,4 +596,9 @@ export type CombineResultsWithAllErrorsArray<
? TraverseWithAllErrors<T>
: Result<ExtractOkTypes<T>, ExtractErrTypes<T>[number][]>

export type PartitionResult<T extends readonly Result<unknown, unknown>[]> = [
ExtractOkTypes<T>[number][],
ExtractErrTypes<T>[number][],
]

//#endregion
Loading