Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/hazae41/result
Rust-like Result for TypeScript
https://github.com/hazae41/result
Last synced: 9 days ago
JSON representation
Rust-like Result for TypeScript
- Host: GitHub
- URL: https://github.com/hazae41/result
- Owner: hazae41
- Created: 2023-03-04T15:14:14.000Z (over 1 year ago)
- Default Branch: master
- Last Pushed: 2024-08-22T15:51:06.000Z (3 months ago)
- Last Synced: 2024-10-18T15:54:07.066Z (21 days ago)
- Language: TypeScript
- Homepage:
- Size: 200 KB
- Stars: 35
- Watchers: 3
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
Awesome Lists containing this project
- awesome-ccamel - hazae41/result - Rust-like Result for TypeScript (TypeScript)
README
# Result
Rust-like Result for TypeScript
```bash
npm i @hazae41/result
```[**Node Package 📦**](https://www.npmjs.com/package/@hazae41/result)
## Features
### Current features
- 100% TypeScript and ESM
- No external dependencies
- Similar to Rust
- `wrap()`/`unwrap()`/`rewrap()` conversion (async/sync)
- `ok()`/`err()` for converting to Option from `@hazae41/option` (with optional chaining `?.`)
- `isOk()`/`isErr()` type guards
- `map()`/`tryMap()` mapping (async/sync)
- `unwrapOr()` default value## Why
When designing a function, you never know how to return that the action failed
### If you throw an Error
This is the standard way of dealing with errors
But you are forced to try-catch, you also need to be aware that the function may throw
```typescript
// does this throw? I don't know
function doSomething(): stringtry {
const result = doSomething()
// use result
} catch(e: unknown) {
// use e (you don't know what it is)
}
```And the error is not typed, so you often end up checking if that's an error, and if it is not, you don't know what to do
```typescript
try {
// ...
} catch(e: unknown) {
if (e instanceof Error)
// use e
else
// what should I do now? rethrow?
}
```### If you return an error
The advantage is that the error is explicit (you know it can fail) and typed
But you have to check for `instanceof Error` each time
```typescript
function doSomething(): string | Errorconst result = doSomething()
if (result instanceof Error)
throw result// use result
```### If you return undefined
The advantage is that you can use optional chaining `?.`
```typescript
function doSomething(): string | undefinedconst maybeSlice = doSomething()?.slice(0, 5)
```But if you want to throw, you have to explicitly check for `undefined`, and the "burden of naming the error" is on you instead of the function you used
```typescript
function doSomething(): string | undefinedconst result = doSomething()
if (result === undefined)
throw new Error(`something failed, idk`)// use result
```And `undefined` may mean something else, for example, a function that reads from IndexedDB:
```typescript
function read(key: string): T | undefined
```Does `undefined` mean that the read failed? Or does it mean that the key doesn't exist?
### If you return a Result
This is the way
It's a simple object that allows you to do all of the methods above, and even more:
- Throw with `unwrap()`
- Get the data and error with `ok()` and `err()`, with support for optional chaining `?.`
- Check the data and error with `isOk()` and `isErr()` type guards
- Map the data and error with `map()` and `mapErr()`
- Use a default value with `unwrapOr()`## Usage
### Unwrapping
Use `unwrap()` to get the inner data if Ok or throw the inner error if Err
```typescript
import { Result, Ok, Err } from "@hazae41/result"function unwrapAndIncrement(result: Result): number {
return result.unwrap() + 1
}unwrapAndIncrement(Ok.new(0)) // will return 1
unwrapAndIncrement(Err.error("Error"))) // will throw Error("Error")
```### Optional
Use `ok()` and `err()` to get an Option, and use `inner` to get the inner value if `Some`, or `undefined` if `None`
```typescript
function maybeSlice(result: Result): string | undefined {
return result.ok().inner?.slice(0, 5)
}maybeSlice(new Ok("hello world")) // will return "hello"
maybeSlice(Err.error("Error")) // will return undefined
```### Safe mapping
You can easily map inner data if Ok and do nothing if Err, with support for async and sync
```typescript
import { Result, Ok, Err } from "@hazae41/result"function tryIncrement(result: Result): Result {
return result.mapSync(x => x + 1)
}tryIncrement(new Ok(0)) // Ok(1)
tryIncrement(Err.error("Error")) // Err(Error("Error"))
```### Type guards
You can easily check for Ok or Err and it's fully type safe
```typescript
import { Result, Ok, Err } from "@hazae41/result"function incrementOrFail(result: Result): number | Error {
if (result.isOk())
result // Ok
else
result // Err
}
```### Wrapping
You can easily wrap try-catch patterns, with support for async and sync
```typescript
const result = Result.runAndWrapSync(() => {
if (something)
return 12345
else
throw new Error("It failed")
})
```### Rewrapping
If another library implements its own Result type, as long as it has `unwrap()`, you can rewrap it to this library in one function
```typescript
interface OtherResult {
unwrap(): T
}function rewrapAndIncrement(other: OtherResult): Result {
return Result.rewrap(other).mapSync(x => x + 1)
}
```### Panicking
When using Result, throwing is seen as "panicking", if something is thrown and is not expected, it should stop the software
So the try-catch pattern is prohibited in Result kingdom, unless you use external code from a library that doesn't use Result
```tsx
try {
return new Ok(doSomethingThatThrows())
} catch(e: unknown) {
return new Err(e as Error)
}
```But, sometimes, you want to do a bunch of actions, unwrap everything, catch everyting and return Err
```tsx
/**
* BAD EXAMPLE
**/
try {
const x = tryDoSomething().unwrap()
const y = tryDoSomething().unwrap()
const z = tryDoSomething().unwrap()return new Ok(doSomethingThatThrows(x, y, z))
} catch(e: unknown) {
return new Err(e as Error)
}
```But what if you only want to catch errors thrown from `Err.unwrap()`, and not errors coming from `doSomethingThatThrows()`?
You can do so by using `Result.unthrow()`, it will do a try-catch but only catch errors coming from `Err.throw()`
```tsx
return Result.unthrowSync(t => {
const x = tryDoSomething().throw(t)
const y = tryDoSomething().throw(t)
const z = tryDoSomething().throw(t)return new Ok(doSomethingThatThrows(x, y, z))
})
```