Skip to content

Commit

Permalink
Add safeTry's docs to README
Browse files Browse the repository at this point in the history
  • Loading branch information
tsuburin committed Oct 26, 2023
1 parent 53d083b commit b8a9016
Show file tree
Hide file tree
Showing 2 changed files with 207 additions and 4 deletions.
84 changes: 81 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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<number, string>;
declare function mayFail2(): Result<number, string>;

function myFunc(): Result<number, string> {
// 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<number, string>;
declare function mayFail2(): Result<number, string>;

function myFunc(): Result<number, string> {
return safeTry<number, string>(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* <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`.
```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*() {
return ok(
// You have to await if the expression is Promise<Result>
(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)
Expand Down
127 changes: 126 additions & 1 deletion tests/safe-try.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {
err,
errAsync,
Ok,
Err
Err,
Result,
ResultAsync,
} from "../src"

describe('Returns what is returned from the generator function', () => {
Expand Down Expand Up @@ -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<number, string> {
return ok(okValue)
}
function bad(): Result<number, string> {
return err(errValue)
}
function promiseGood(): Promise<Result<number, string>> {
return Promise.resolve(ok(okValue))
}
function promiseBad(): Promise<Result<number, string>> {
return Promise.resolve(err(errValue))
}
function asyncGood(): ResultAsync<number, string> {
return okAsync(okValue)
}
function asyncBad(): ResultAsync<number, string> {
return errAsync(errValue)
}

test("mayFail2 error", () => {
function myFunc(): Result<number, string> {
return safeTry<number, string>(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<number, string> {
return safeTry<number, string>(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<Result<number, string>> {
return safeTry<number, string>(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<Result<number, string>> {
return safeTry<number, string>(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<Result<number, string>> {
return safeTry<number, string>(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)
})
})

0 comments on commit b8a9016

Please sign in to comment.