From b8a90169c11fc34478895b659bdc5b2289f345d1 Mon Sep 17 00:00:00 2001 From: tsuburin Date: Thu, 26 Oct 2023 11:07:15 +0900 Subject: [PATCH] Add safeTry's docs to README --- README.md | 84 ++++++++++++++++++++++++++- tests/safe-try.test.ts | 127 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 207 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 90ed8b2b..6db5c415 100644 --- a/README.md +++ b/README.md @@ -664,7 +664,7 @@ const result = Result.combineWithAllErrors(resultList) #### `Result.safeUnwrap()` -**⚠️ You must use `.safeUnwrap` in a generator context with `safeTry`** +**⚠️ You must use `.safeUnwrap` in a generator context with `safeTry`**. Please see [safeTry](#safeTry). Allows for unwrapping a `Result` or returning an `Err` implicitly, thereby reducing boilerplate. @@ -1140,7 +1140,7 @@ const result = ResultAsync.combineWithAllErrors(resultList) #### `ResultAsync.safeUnwrap()` -**⚠️ You must use `.safeUnwrap` in a generator context with `safeTry`** +**⚠️ You must use `.safeUnwrap` in a generator context with `safeTry`**. Please see [safeTry](#safeTry). Allows for unwrapping a `Result` or returning an `Err` implicitly, thereby reducing boilerplate. @@ -1176,7 +1176,85 @@ Please find documentation at [ResultAsync.fromSafePromise](#resultasyncfromsafep Used to implicityly return errors and reduce boilerplate. -See https://github.com/supermacro/neverthrow/pull/448 and https://github.com/supermacro/neverthrow/issues/444 +Let's say we are writing a function that returns a `Result`, and in that function we call some functions which also return `Result`s and we check those results to see whether we shold keep going or abort. Usually, we will write like the following. +```typescript +declare function mayFail1(): Result; +declare function mayFail2(): Result; + +function myFunc(): Result { + // We have to define a constant to hold the result to check and unwrap its value. + const result1 = mayFail1(); + if (result1.isErr()) { + return err(`aborted by an error from 1st function, ${result1.error}`); + } + const value1 = result1.value + + // Again, we need to define a constant and then check and unwrap. + const result2 = mayFail2(); + if (result2.isErr()) { + return err(`aborted by an error from 2nd function, ${result2.error}`); + } + const value2 = result2.value + + // And finally we return what we want to calculate + return ok(value1 + value2); +} +``` +Basically, we need to define a constant for each result to check whether it's a `Ok` and read its `.value` or `.error`. + +With safeTry, we can state 'Return here if its an `Err`, otherwise unwrap it here and keep going.' in just one expression. +```typescript +declare function mayFail1(): Result; +declare function mayFail2(): Result; + +function myFunc(): Result { + 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`. + // Otherwise, this `(yield* ...)` is evaluated to its `.value`. + (yield* mayFail1() + .mapErr(e => `aborted by an error from 1st function, ${e}`) + .safeUnwrap()) + + + // The same as above. + (yield* mayFail2() + .mapErr(e => `aborted by an error from 2nd function, ${e}`) + .safeUnwrap()) + ) + }) +} +``` + +To use `safeTry`, the points are as follows. +* Wrap the entire block in a [generator function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) +* 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`. +```typescript +// You can use either Promise or ResultAsync. +declare function mayFail1(): Promise>; +declare function mayFail2(): ResultAsync; + +function myFunc(): Promise> { + return safeTry(async function*() { + return ok( + // You have to await if the expression is Promise + (yield* (await mayFail1()) + .mapErr(e => `aborted by an error from 1st function, ${e}`) + .safeUnwrap()) + + + // You can call `safeUnwrap` directly if its ResultAsync + (yield* mayFail2() + .mapErr(e => `aborted by an error from 2nd function, ${e}`) + .safeUnwrap()) + ) + }) +} +``` + +For more information, see https://github.com/supermacro/neverthrow/pull/448 and https://github.com/supermacro/neverthrow/issues/444 [⬆️ Back to top](#toc) diff --git a/tests/safe-try.test.ts b/tests/safe-try.test.ts index a6503459..3983ae9f 100644 --- a/tests/safe-try.test.ts +++ b/tests/safe-try.test.ts @@ -5,7 +5,9 @@ import { err, errAsync, Ok, - Err + Err, + Result, + ResultAsync, } from "../src" describe('Returns what is returned from the generator function', () => { @@ -111,3 +113,126 @@ describe("Returns the first occurence of Err instance as yiled*'s operand", () = expect(result._unsafeUnwrapErr()).toBe(errVal) }) }) + +describe("Tests if README's examples work", () => { + const okValue = 3 + const errValue = "err!" + function good(): Result { + return ok(okValue) + } + function bad(): Result { + return err(errValue) + } + function promiseGood(): Promise> { + return Promise.resolve(ok(okValue)) + } + function promiseBad(): Promise> { + return Promise.resolve(err(errValue)) + } + function asyncGood(): ResultAsync { + return okAsync(okValue) + } + function asyncBad(): ResultAsync { + return errAsync(errValue) + } + + test("mayFail2 error", () => { + function myFunc(): Result { + return safeTry(function*() { + return ok( + (yield* good() + .mapErr(e => `1st, ${e}`) + .safeUnwrap()) + + + (yield* bad() + .mapErr(e => `2nd, ${e}`) + .safeUnwrap()) + ) + }) + } + + const result = myFunc() + expect(result.isErr()).toBe(true) + expect(result._unsafeUnwrapErr()).toBe(`2nd, ${errValue}`) + }) + + test("all ok", () => { + function myFunc(): Result { + return safeTry(function*() { + return ok( + (yield* good() + .mapErr(e => `1st, ${e}`) + .safeUnwrap()) + + + (yield* good() + .mapErr(e => `2nd, ${e}`) + .safeUnwrap()) + ) + }) + } + + const result = myFunc() + expect(result.isOk()).toBe(true) + expect(result._unsafeUnwrap()).toBe(okValue + okValue) + }) + + test("async mayFail1 error", async () => { + function myFunc(): Promise> { + return safeTry(async function*() { + return ok( + (yield* (await promiseBad()) + .mapErr(e => `1st, ${e}`) + .safeUnwrap()) + + + (yield* asyncGood() + .mapErr(e => `2nd, ${e}`) + .safeUnwrap()) + ) + }) + } + + const result = await myFunc() + expect(result.isErr()).toBe(true) + expect(result._unsafeUnwrapErr()).toBe(`1st, ${errValue}`) + }) + + test("async mayFail2 error", async () => { + function myFunc(): Promise> { + return safeTry(async function*() { + return ok( + (yield* (await promiseGood()) + .mapErr(e => `1st, ${e}`) + .safeUnwrap()) + + + (yield* asyncBad() + .mapErr(e => `2nd, ${e}`) + .safeUnwrap()) + ) + }) + } + + const result = await myFunc() + expect(result.isErr()).toBe(true) + expect(result._unsafeUnwrapErr()).toBe(`2nd, ${errValue}`) + }) + + test("promise async all ok", async () => { + function myFunc(): Promise> { + return safeTry(async function*() { + return ok( + (yield* (await promiseGood()) + .mapErr(e => `1st, ${e}`) + .safeUnwrap()) + + + (yield* asyncGood() + .mapErr(e => `2nd, ${e}`) + .safeUnwrap()) + ) + }) + } + + const result = await myFunc() + expect(result.isOk()).toBe(true) + expect(result._unsafeUnwrap()).toBe(okValue + okValue) + }) +})