Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add safeTry's docs to README #499

Merged
merged 1 commit into from
Oct 30, 2023
Merged
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
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)
})
})