Skip to content
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
30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1582,7 +1582,7 @@ declare function mayFail1(): Result<number, string>;
declare function mayFail2(): Result<number, string>;

function myFunc(): Result<number, string> {
return safeTry<number, string>(function*() {
return safeTry(function*() {
return ok(
// If the result of mayFail1().mapErr() is an `Err`, the evaluation is
// aborted here and the enclosing `safeTry` block is evaluated to that `Err`.
Expand All @@ -1603,14 +1603,15 @@ To use `safeTry`, the points are as follows.
* In that block, you can use `yield* <RESULT>` to state 'Return `<RESULT>` if it's an `Err`, otherwise evaluate to its `.value`'
* Pass the generator function to `safeTry`

You can also use [async generator function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function*) to pass an async block to `safeTry`.
You can also use [async generator function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function*) to pass an async block to `safeTry`. For more information, see https://github.com/supermacro/neverthrow/pull/448 and https://github.com/supermacro/neverthrow/issues/444

```typescript
// You can use either Promise<Result> or ResultAsync.
declare function mayFail1(): Promise<Result<number, string>>;
declare function mayFail2(): ResultAsync<number, string>;

function myFunc(): Promise<Result<number, string>> {
return safeTry<number, string>(async function*() {
function myFunc(): ResultAsync<number, string> {
return safeTry(async function*() {
return ok(
// You have to await if the expression is Promise<Result>
(yield* (await mayFail1())
Expand All @@ -1624,7 +1625,26 @@ function myFunc(): Promise<Result<number, string>> {
}
```

For more information, see https://github.com/supermacro/neverthrow/pull/448 and https://github.com/supermacro/neverthrow/issues/444
Additionally, when using `safeTry` in class methods, you can pass `this` context as the first argument:

```typescript
class MyClass {
mayFail1: () => Promise<Result<number, string>>;
mayFail2: () => ResultAsync<number, string>;

myFunc(): ResultAsync<number, string> {
return safeTry(this, async function*() {
return ok(
(yield* (await this.mayFail1())
.mapErr(e => `aborted by an error from 1st function, ${e}`))
+
(yield* this.mayFail2()
.mapErr(e => `aborted by an error from 2nd function, ${e}`))
)
})
}
}
```

[⬆️ Back to top](#toc)

Expand Down
102 changes: 97 additions & 5 deletions src/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,23 @@ export function err<T = never, E = unknown>(err: E): Err<T, E> {
* @returns The first occurrence of either an yielded Err or a returned Result.
*/
export function safeTry<T, E>(body: () => Generator<Err<never, E>, Result<T, E>>): Result<T, E>

/**
* Evaluates the given generator to a Result returned or an Err yielded from it,
* whichever comes first.
*
* This function is intended to emulate Rust's ? operator.
* See `/tests/safeTry.test.ts` for examples.
*
* @param thisArg - The `this` context to bind to the generator function
* @param body - What is evaluated. In body, `yield* result` works as
* Rust's `result?` expression.
* @returns The first occurrence of either an yielded Err or a returned Result.
*/
export function safeTry<T, E, This>(
thisArg: This,
body: (this: This) => Generator<Err<never, E>, Result<T, E>>,
): Result<T, E>
export function safeTry<
YieldErr extends Err<never, unknown>,
GeneratorReturnResult extends Result<unknown, unknown>
Expand All @@ -96,6 +113,30 @@ export function safeTry<
InferErrTypes<YieldErr> | InferErrTypes<GeneratorReturnResult>
>

/**
* Evaluates the given generator to a Result returned or an Err yielded from it,
* whichever comes first.
*
* This function is intended to emulate Rust's ? operator.
* See `/tests/safeTry.test.ts` for examples.
*
* @param thisArg - The `this` context to bind to the generator function
* @param body - What is evaluated. In body, `yield* result` works as
* Rust's `result?` expression.
* @returns The first occurrence of either an yielded Err or a returned Result.
*/
export function safeTry<
YieldErr extends Err<never, unknown>,
GeneratorReturnResult extends Result<unknown, unknown>,
This
>(
thisArg: This,
body: (this: This) => Generator<YieldErr, GeneratorReturnResult>,
): Result<
InferOkTypes<GeneratorReturnResult>,
InferErrTypes<YieldErr> | InferErrTypes<GeneratorReturnResult>
>

/**
* Evaluates the given generator to a Result returned or an Err yielded from it,
* whichever comes first.
Expand All @@ -110,6 +151,24 @@ export function safeTry<
export function safeTry<T, E>(
body: () => AsyncGenerator<Err<never, E>, Result<T, E>>,
): ResultAsync<T, E>

/**
* Evaluates the given generator to a Result returned or an Err yielded from it,
* whichever comes first.
*
* This function is intended to emulate Rust's ? operator.
* See `/tests/safeTry.test.ts` for examples.
*
* @param thisArg - The `this` context to bind to the generator function
* @param body - What is evaluated. In body, `yield* result` and
* `yield* resultAsync` work as Rust's `result?` expression.
* @returns The first occurrence of either an yielded Err or a returned Result.
*/
export function safeTry<T, E, This>(
thisArg: This,
body: (this: This) => AsyncGenerator<Err<never, E>, Result<T, E>>,
): ResultAsync<T, E>

export function safeTry<
YieldErr extends Err<never, unknown>,
GeneratorReturnResult extends Result<unknown, unknown>
Expand All @@ -119,18 +178,51 @@ export function safeTry<
InferOkTypes<GeneratorReturnResult>,
InferErrTypes<YieldErr> | InferErrTypes<GeneratorReturnResult>
>
export function safeTry<T, E>(
body:
| (() => Generator<Err<never, E>, Result<T, E>>)
| (() => AsyncGenerator<Err<never, E>, Result<T, E>>),

/**
* Evaluates the given generator to a Result returned or an Err yielded from it,
* whichever comes first.
*
* This function is intended to emulate Rust's ? operator.
* See `/tests/safeTry.test.ts` for examples.
*
* @param thisArg - The `this` context to bind to the generator function
* @param body - What is evaluated. In body, `yield* result` and
* `yield* resultAsync` work as Rust's `result?` expression.
* @returns The first occurrence of either an yielded Err or a returned Result.
*/
export function safeTry<
YieldErr extends Err<never, unknown>,
GeneratorReturnResult extends Result<unknown, unknown>,
This
>(
thisArg: This,
body: (this: This) => AsyncGenerator<YieldErr, GeneratorReturnResult>,
): ResultAsync<
InferOkTypes<GeneratorReturnResult>,
InferErrTypes<YieldErr> | InferErrTypes<GeneratorReturnResult>
>

export function safeTry<T, E, This>(
bodyOrThisArg: (() => ResultReturnGenerator<T, E>) | This,
body?: (this: This) => ResultReturnGenerator<T, E>,
): Result<T, E> | ResultAsync<T, E> {
const n = body().next()
const g =
body == undefined
? (bodyOrThisArg as () => ResultReturnGenerator<T, E>)()
: (body.call(bodyOrThisArg) as ResultReturnGenerator<T, E>)
const n = g.next()

if (n instanceof Promise) {
return new ResultAsync(n.then((r) => r.value))
}
return n.value
}

type ResultReturnGenerator<T, E> =
| Generator<Err<never, E>, Result<T, E>>
| AsyncGenerator<Err<never, E>, Result<T, E>>

interface IResult<T, E> {
/**
* Used to check if a `Result` is an `OK`
Expand Down
32 changes: 32 additions & 0 deletions tests/safe-try.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ describe('Returns what is returned from the generator function', () => {
expect(res._unsafeUnwrap()).toBe(val)
})

test("With synchronous Ok and this context", () => {
const res = safeTry(val, function*() {
return ok(this)
})
expect(res).toBeInstanceOf(Ok)
expect(res._unsafeUnwrap()).toBe(val)
})

test("With synchronous Err", () => {
const res = safeTry(function*() {
return err(val)
Expand All @@ -31,6 +39,14 @@ describe('Returns what is returned from the generator function', () => {
expect(res._unsafeUnwrapErr()).toBe(val)
})

test("With synchronous Err and this context", () => {
const res = safeTry(val, function*() {
return err(this)
})
expect(res).toBeInstanceOf(Err)
expect(res._unsafeUnwrapErr()).toBe(val)
})

test("With async Ok", async () => {
const res = await safeTry(async function*() {
return await okAsync(val)
Expand All @@ -39,13 +55,29 @@ describe('Returns what is returned from the generator function', () => {
expect(res._unsafeUnwrap()).toBe(val)
})

test("With async Ok and this context", async () => {
const res = await safeTry(val, async function*() {
return await okAsync(this)
})
expect(res).toBeInstanceOf(Ok)
expect(res._unsafeUnwrap()).toBe(val)
})

test("With async Err", async () => {
const res = await safeTry(async function*() {
return await errAsync(val)
})
expect(res).toBeInstanceOf(Err)
expect(res._unsafeUnwrapErr()).toBe(val)
})

test("With async Err and this context", async () => {
const res = await safeTry(val, async function*() {
return await errAsync(this)
})
expect(res).toBeInstanceOf(Err)
expect(res._unsafeUnwrapErr()).toBe(val)
})
})

describe("Returns the first occurence of Err instance as yiled*'s operand", () => {
Expand Down