Rust-like Result for TypeScript
npm i @hazae41/result
- 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 guardsmap()
/tryMap()
mapping (async/sync)unwrapOr()
default value
When designing a function, you never know how to return that the action failed
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
// does this throw? I don't know
function doSomething(): string
try {
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
try {
// ...
} catch(e: unknown) {
if (e instanceof Error)
// use e
else
// what should I do now?
}
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
function doSomething(): string | Error
const result = doSomething()
if (result instanceof Error)
throw result
// use result
The advantage is that you can use optional chaining ?.
function doSomething(): string | undefined
const 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
function doSomething(): string | undefined
const 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:
function read<T>(key: string): T | undefined
Does undefined
mean that the read failed? Or does it mean that the key doesn't exist?
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()
anderr()
, with support for optional chaining?.
- Check the data and error with
isOk()
andisErr()
type guards - Map the data and error with
map()
andmapErr()
- Use a default value with
unwrapOr()
Use unwrap()
to get the inner data if Ok or throw the inner error if Err
import { Result, Ok, Err } from "@hazae41/result"
function unwrapAndIncrement(result: Result<number>): number {
return result.unwrap() + 1
}
unwrapAndIncrement(Ok.new(0)) // will return 1
unwrapAndIncrement(Err.error("Error"))) // will throw Error("Error")
Use ok()
and err()
to get an Option, and use inner
to get the inner value if Some
, or undefined
if None
function maybeSlice(result: Result<string>): string | undefined {
return result.ok().inner?.slice(0, 5)
}
maybeSlice(Ok.new("hello world")) // will return "hello"
maybeSlice(Err.error("Error")) // will return undefined
You can easily map inner data if Ok and do nothing if Err, with support for async and sync
import { Result, Ok, Err } from "@hazae41/result"
function tryIncrement(result: Result<number, Error>): Result<number> {
return result.mapSync(x => x + 1)
}
tryIncrement(Ok.new(0)) // Ok(1)
tryIncrement(Err.error("Error")) // Err(Error("Error"))
You can easily check for Ok or Err and it's fully type safe
import { Result, Ok, Err } from "@hazae41/result"
function incrementOrFail(result: Result<number, Error>): number | Error {
if (result.isOk()) // Ok<number>
return result.inner + 1 // number
else // Err<Error>
return new Error("Failed", { cause: result.inner })
}
You can easily wrap try-catch patterns, with support for async and sync
const result = Result.tryWrapSync(() => {
if (something)
return 12345
else
throw new Error("It failed")
})
If another library implements its own Result type, as long as it has unwrap()
, you can rewrap it to this library in one function
interface OtherResult<T> {
unwrap(): T
}
function rewrapAndIncrement(other: OtherResult<number>): Result<number> {
return Result.rewrap(other).tryMapSync(x => x + 1)
}