Skip to content

Commit 366dbed

Browse files
authored
Merge branch 'master' into master
2 parents 77c430f + 1d4cc19 commit 366dbed

File tree

8 files changed

+198
-8
lines changed

8 files changed

+198
-8
lines changed

.changeset/thirty-avocados-explain.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
# neverthrow
22

3+
## 8.2.0
4+
5+
### Minor Changes
6+
7+
- [#615](https://github.com/supermacro/neverthrow/pull/615) [`85ed7fd`](https://github.com/supermacro/neverthrow/commit/85ed7fd3a1247e4c0e83bba13f5e874282243d75) Thanks [@konker](https://github.com/konker)! - Add orTee, which is the equivalent of andTee but for the error track.
8+
9+
- [#584](https://github.com/supermacro/neverthrow/pull/584) [`acea44a`](https://github.com/supermacro/neverthrow/commit/acea44adb98dda2ca32fe4e882879461cc7cedc2) Thanks [@macksal](https://github.com/macksal)! - Allow ok/err/okAsync/errAsync to accept zero arguments when returning void
10+
311
## 8.1.1
412

513
### Patch Changes

README.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ For asynchronous tasks, `neverthrow` offers a `ResultAsync` class which wraps a
3636
- [`Result.match` (method)](#resultmatch-method)
3737
- [`Result.asyncMap` (method)](#resultasyncmap-method)
3838
- [`Result.andTee` (method)](#resultandtee-method)
39+
- [`Result.orTee` (method)](#resultortee-method)
3940
- [`Result.andThrough` (method)](#resultandthrough-method)
4041
- [`Result.asyncAndThrough` (method)](#resultasyncandthrough-method)
4142
- [`Result.fromThrowable` (static class method)](#resultfromthrowable-static-class-method)
@@ -55,6 +56,7 @@ For asynchronous tasks, `neverthrow` offers a `ResultAsync` class which wraps a
5556
- [`ResultAsync.orElse` (method)](#resultasyncorelse-method)
5657
- [`ResultAsync.match` (method)](#resultasyncmatch-method)
5758
- [`ResultAsync.andTee` (method)](#resultasyncandtee-method)
59+
- [`ResultAsync.orTee` (method)](#resultasyncortee-method)
5860
- [`ResultAsync.andThrough` (method)](#resultasyncandthrough-method)
5961
- [`ResultAsync.combine` (static class method)](#resultasynccombine-static-class-method)
6062
- [`ResultAsync.combineWithAllErrors` (static class method)](#resultasynccombinewithallerrors-static-class-method)
@@ -593,6 +595,53 @@ resAsync.then((res: Result<void, ParseError | InsertError>) => {e
593595

594596
---
595597

598+
#### `Result.orTee` (method)
599+
600+
Like `andTee` for the error track. Takes a `Result<T, E>` and lets the `Err` value pass through regardless the result of the passed-in function.
601+
This is a handy way to handle side effects whose failure or success should not affect your main logics such as logging.
602+
603+
**Signature:**
604+
605+
```typescript
606+
class Result<T, E> {
607+
orTee(
608+
callback: (value: E) => unknown
609+
): Result<T, E> { ... }
610+
}
611+
```
612+
613+
**Example:**
614+
615+
```typescript
616+
import { parseUserInput } from 'imaginary-parser'
617+
import { logParseError } from 'imaginary-logger'
618+
import { insertUser } from 'imaginary-database'
619+
620+
// ^ assume parseUserInput, logParseError and insertUser have the following signatures:
621+
// parseUserInput(input: RequestData): Result<User, ParseError>
622+
// logParseError(parseError: ParseError): Result<void, LogError>
623+
// insertUser(user: User): ResultAsync<void, InsertError>
624+
// Note logParseError returns void upon success but insertUser takes User type.
625+
626+
const resAsync = parseUserInput(userInput)
627+
.orTee(logParseError)
628+
.asyncAndThen(insertUser)
629+
630+
// Note no LogError shows up in the Result type
631+
resAsync.then((res: Result<void, ParseError | InsertError>) => {e
632+
if(res.isErr()){
633+
console.log("Oops, at least one step failed", res.error)
634+
}
635+
else{
636+
console.log("User input has been parsed and inserted successfully.")
637+
}
638+
}))
639+
```
640+
641+
[⬆️ Back to top](#toc)
642+
643+
---
644+
596645
#### `Result.andThrough` (method)
597646

598647
Similar to `andTee` except for:
@@ -1277,6 +1326,53 @@ resAsync.then((res: Result<void, InsertError | NotificationError>) => {e
12771326

12781327
[⬆️ Back to top](#toc)
12791328

1329+
---
1330+
#### `ResultAsync.orTee` (method)
1331+
1332+
Like `andTee` for the error track. Takes a `ResultAsync<T, E>` and lets the original `Err` value pass through regardless
1333+
the result of the passed-in function.
1334+
This is a handy way to handle side effects whose failure or success should not affect your main logics such as logging.
1335+
1336+
**Signature:**
1337+
1338+
```typescript
1339+
class ResultAsync<T, E> {
1340+
orTee(
1341+
callback: (value: E) => unknown
1342+
): ResultAsync<T, E> => { ... }
1343+
}
1344+
```
1345+
1346+
**Example:**
1347+
1348+
```typescript
1349+
import { insertUser } from 'imaginary-database'
1350+
import { logInsertError } from 'imaginary-logger'
1351+
import { sendNotification } from 'imaginary-service'
1352+
1353+
// ^ assume insertUser, logInsertError and sendNotification have the following signatures:
1354+
// insertUser(user: User): ResultAsync<User, InsertError>
1355+
// logInsertError(insertError: InsertError): Result<void, LogError>
1356+
// sendNotification(user: User): ResultAsync<void, NotificationError>
1357+
// Note logInsertError returns void on success but sendNotification takes User type.
1358+
1359+
const resAsync = insertUser(user)
1360+
.orTee(logUser)
1361+
.andThen(sendNotification)
1362+
1363+
// Note there is no LogError in the types below
1364+
resAsync.then((res: Result<void, InsertError | NotificationError>) => {e
1365+
if(res.isErr()){
1366+
console.log("Oops, at least one step failed", res.error)
1367+
}
1368+
else{
1369+
console.log("User has been inserted and notified successfully.")
1370+
}
1371+
}))
1372+
```
1373+
1374+
[⬆️ Back to top](#toc)
1375+
12801376
---
12811377
#### `ResultAsync.andThrough` (method)
12821378

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "neverthrow",
3-
"version": "8.1.1",
3+
"version": "8.2.0",
44
"description": "Stop throwing errors, and instead return Results!",
55
"main": "dist/index.cjs.js",
66
"module": "dist/index.es.js",

src/result-async.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,22 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
139139
)
140140
}
141141

142+
orTee(f: (t: E) => unknown): ResultAsync<T, E> {
143+
return new ResultAsync(
144+
this._promise.then(async (res: Result<T, E>) => {
145+
if (res.isOk()) {
146+
return new Ok<T, E>(res.value)
147+
}
148+
try {
149+
await f(res.error)
150+
} catch (e) {
151+
// Tee does not care about the error
152+
}
153+
return new Err<T, E>(res.error)
154+
}),
155+
)
156+
}
157+
142158
mapErr<U>(f: (e: E) => U | Promise<U>): ResultAsync<T, U> {
143159
return new ResultAsync(
144160
this._promise.then(async (res: Result<T, E>) => {

src/result.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,18 @@ interface IResult<T, E> {
209209
*/
210210
andTee(f: (t: T) => unknown): Result<T, E>
211211

212+
/**
213+
* This "tee"s the current `Err` value to an passed-in computation such as side
214+
* effect functions but still returns the same `Err` value as the result.
215+
*
216+
* This is useful when you want to pass the current `Err` value to your side-track
217+
* work such as logging but want to continue error-track work after that.
218+
* This method does not care about the result of the passed in computation.
219+
*
220+
* @param f The function to apply to the current `Err` value
221+
*/
222+
orTee(f: (t: E) => unknown): Result<T, E>
223+
212224
/**
213225
* Similar to `andTee` except error result of the computation will be passed
214226
* to the downstream in case of an error.
@@ -357,6 +369,10 @@ export class Ok<T, E> implements IResult<T, E> {
357369
return ok<T, E>(this.value)
358370
}
359371

372+
orTee(_f: (t: E) => unknown): Result<T, E> {
373+
return ok<T, E>(this.value)
374+
}
375+
360376
orElse<R extends Result<unknown, unknown>>(
361377
_f: (e: E) => R,
362378
): Result<InferOkTypes<R> | T, InferErrTypes<R>>
@@ -443,6 +459,15 @@ export class Err<T, E> implements IResult<T, E> {
443459
return err(this.error)
444460
}
445461

462+
orTee(f: (t: E) => unknown): Result<T, E> {
463+
try {
464+
f(this.error)
465+
} catch (e) {
466+
// Tee doesn't care about the error
467+
}
468+
return err<T, E>(this.error)
469+
}
470+
446471
andThen<R extends Result<unknown, unknown>>(
447472
_f: (t: T) => R,
448473
): Result<InferOkTypes<R>, InferErrTypes<R> | E>

tests/index.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,31 @@ describe('Result.Ok', () => {
159159
})
160160
})
161161

162+
describe('orTee', () => {
163+
it('Calls the passed function but returns an original err', () => {
164+
const errVal = err(12)
165+
const passedFn = vitest.fn((_number) => {})
166+
167+
const teed = errVal.orTee(passedFn)
168+
169+
expect(teed.isErr()).toBe(true)
170+
expect(passedFn).toHaveBeenCalledTimes(1)
171+
expect(teed._unsafeUnwrapErr()).toStrictEqual(12)
172+
})
173+
it('returns an original err even when the passed function fails', () => {
174+
const errVal = err(12)
175+
const passedFn = vitest.fn((_number) => {
176+
throw new Error('OMG!')
177+
})
178+
179+
const teed = errVal.orTee(passedFn)
180+
181+
expect(teed.isErr()).toBe(true)
182+
expect(passedFn).toHaveBeenCalledTimes(1)
183+
expect(teed._unsafeUnwrapErr()).toStrictEqual(12)
184+
})
185+
})
186+
162187
describe('asyncAndThrough', () => {
163188
it('Calls the passed function but returns an original ok as Async', async () => {
164189
const okVal = ok(12)
@@ -1064,6 +1089,31 @@ describe('ResultAsync', () => {
10641089
})
10651090
})
10661091

1092+
describe('orTee', () => {
1093+
it('Calls the passed function but returns an original err', async () => {
1094+
const errVal = errAsync(12)
1095+
const passedFn = vitest.fn((_number) => {})
1096+
1097+
const teed = await errVal.orTee(passedFn)
1098+
1099+
expect(teed.isErr()).toBe(true)
1100+
expect(passedFn).toHaveBeenCalledTimes(1)
1101+
expect(teed._unsafeUnwrapErr()).toStrictEqual(12)
1102+
})
1103+
it('returns an original err even when the passed function fails', async () => {
1104+
const errVal = errAsync(12)
1105+
const passedFn = vitest.fn((_number) => {
1106+
throw new Error('OMG!')
1107+
})
1108+
1109+
const teed = await errVal.orTee(passedFn)
1110+
1111+
expect(teed.isErr()).toBe(true)
1112+
expect(passedFn).toHaveBeenCalledTimes(1)
1113+
expect(teed._unsafeUnwrapErr()).toStrictEqual(12)
1114+
})
1115+
})
1116+
10671117
describe('orElse', () => {
10681118
it('Skips orElse on an Ok value', async () => {
10691119
const okVal = okAsync(12)

0 commit comments

Comments
 (0)