diff --git a/README.md b/README.md index cfe6b7be..a53f708f 100644 --- a/README.md +++ b/README.md @@ -1582,7 +1582,7 @@ declare function mayFail1(): Result; declare function mayFail2(): Result; function myFunc(): Result { - return safeTry(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`. @@ -1603,14 +1603,15 @@ To use `safeTry`, the points are as follows. * In that block, you can use `yield* ` to state 'Return `` 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 or ResultAsync. declare function mayFail1(): Promise>; declare function mayFail2(): ResultAsync; -function myFunc(): Promise> { - return safeTry(async function*() { +function myFunc(): ResultAsync { + return safeTry(async function*() { return ok( // You have to await if the expression is Promise (yield* (await mayFail1()) @@ -1624,7 +1625,26 @@ function myFunc(): Promise> { } ``` -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>; + mayFail2: () => ResultAsync; + + myFunc(): ResultAsync { + 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) diff --git a/src/result.ts b/src/result.ts index ad447caa..1b46fb6b 100644 --- a/src/result.ts +++ b/src/result.ts @@ -86,6 +86,23 @@ export function err(err: E): Err { * @returns The first occurrence of either an yielded Err or a returned Result. */ export function safeTry(body: () => Generator, Result>): Result + +/** + * 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( + thisArg: This, + body: (this: This) => Generator, Result>, +): Result export function safeTry< YieldErr extends Err, GeneratorReturnResult extends Result @@ -96,6 +113,30 @@ export function safeTry< InferErrTypes | InferErrTypes > +/** + * 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, + GeneratorReturnResult extends Result, + This +>( + thisArg: This, + body: (this: This) => Generator, +): Result< + InferOkTypes, + InferErrTypes | InferErrTypes +> + /** * Evaluates the given generator to a Result returned or an Err yielded from it, * whichever comes first. @@ -110,6 +151,24 @@ export function safeTry< export function safeTry( body: () => AsyncGenerator, Result>, ): ResultAsync + +/** + * 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( + thisArg: This, + body: (this: This) => AsyncGenerator, Result>, +): ResultAsync + export function safeTry< YieldErr extends Err, GeneratorReturnResult extends Result @@ -119,18 +178,51 @@ export function safeTry< InferOkTypes, InferErrTypes | InferErrTypes > -export function safeTry( - body: - | (() => Generator, Result>) - | (() => AsyncGenerator, Result>), + +/** + * 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, + GeneratorReturnResult extends Result, + This +>( + thisArg: This, + body: (this: This) => AsyncGenerator, +): ResultAsync< + InferOkTypes, + InferErrTypes | InferErrTypes +> + +export function safeTry( + bodyOrThisArg: (() => ResultReturnGenerator) | This, + body?: (this: This) => ResultReturnGenerator, ): Result | ResultAsync { - const n = body().next() + const g = + body == undefined + ? (bodyOrThisArg as () => ResultReturnGenerator)() + : (body.call(bodyOrThisArg) as ResultReturnGenerator) + const n = g.next() + if (n instanceof Promise) { return new ResultAsync(n.then((r) => r.value)) } return n.value } +type ResultReturnGenerator = + | Generator, Result> + | AsyncGenerator, Result> + interface IResult { /** * Used to check if a `Result` is an `OK` diff --git a/tests/safe-try.test.ts b/tests/safe-try.test.ts index 71fee7d9..82334760 100644 --- a/tests/safe-try.test.ts +++ b/tests/safe-try.test.ts @@ -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) @@ -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) @@ -39,6 +55,14 @@ 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) @@ -46,6 +70,14 @@ describe('Returns what is returned from the generator function', () => { 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", () => {