From fd522b755895f7ba72ba4839a66726da3f904b1f Mon Sep 17 00:00:00 2001 From: Yuichi Goto <949545+yasaichi@users.noreply.github.com> Date: Sun, 10 Aug 2025 21:55:09 +0100 Subject: [PATCH 1/4] feat: add this context support to safeTry function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add overloads for safeTry to accept `this` context as first parameter - Support both sync and async generators with proper type inference - Maintain backward compatibility with existing API - Implement GitHub issue #632 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/result.ts | 102 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 5 deletions(-) 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` From 21c5917452d687782cb302ec6fa67eefb5232071 Mon Sep 17 00:00:00 2001 From: Yuichi Goto <949545+yasaichi@users.noreply.github.com> Date: Sun, 10 Aug 2025 22:11:33 +0100 Subject: [PATCH 2/4] test: add tests for safeTry with this context support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add tests for sync/async generators with this context - Tests mirror existing test structure for consistency - All 121 tests passing 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- tests/safe-try.test.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) 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", () => { From 67c418af728fec8bd25f4665900d71bf480099a5 Mon Sep 17 00:00:00 2001 From: Yuichi Goto <949545+yasaichi@users.noreply.github.com> Date: Sun, 10 Aug 2025 22:45:16 +0100 Subject: [PATCH 3/4] docs: add this context support documentation to README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add example usage of safeTry with this context in class methods - Use async generator example demonstrating this binding - Use type inference approach for cleaner code examples 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0a36c081..8ea4a2ef 100644 --- a/README.md +++ b/README.md @@ -1603,7 +1603,8 @@ 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>; @@ -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) From 292ceb4b78cdc404b40d812251a46d498b8bedd0 Mon Sep 17 00:00:00 2001 From: Yuichi Goto <949545+yasaichi@users.noreply.github.com> Date: Sun, 10 Aug 2025 22:47:38 +0100 Subject: [PATCH 4/4] docs: improve consistency in safeTry documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove explicit type parameters from examples to rely on type inference - Fix async generator return type from Promise to ResultAsync 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8ea4a2ef..dfb33277 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`. @@ -1610,8 +1610,8 @@ You can also use [async generator function](https://developer.mozilla.org/en-US/ 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())