Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Rust-like Result for TypeScript

NotificationsYou must be signed in to change notification settings

hazae41/result

Repository files navigation

Rust-like Result for TypeScript

npm i @hazae41/result

Node Package 📦

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

// does this throw? I don't knowfunctiondoSomething():stringtry{constresult=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(einstanceofError)// use eelse// 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 forinstanceof Error each time

functiondoSomething():string|Errorconstresult=doSomething()if(resultinstanceofError)throwresult// use result

If you return undefined

The advantage is that you can use optional chaining?.

functiondoSomething():string|undefinedconstmaybeSlice=doSomething()?.slice(0,5)

But if you want to throw, you have to explicitly check forundefined, and the "burden of naming the error" is on you instead of the function you used

functiondoSomething():string|undefinedconstresult=doSomething()if(result===undefined)thrownewError(`something failed, idk`)// use result

Andundefined may mean something else, for example, a function that reads from IndexedDB:

functionread<T>(key:string):T|undefined

Doesundefined 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 withunwrap()
  • Get the data and error withok() anderr(), with support for optional chaining?.
  • Check the data and error withisOk() andisErr() type guards
  • Map the data and error withmap() andmapErr()
  • Use a default value withunwrapOr()

Usage

Unwrapping

Useunwrap() to get the inner data if Ok or throw the inner error if Err

import{Result,Ok,Err}from"@hazae41/result"functionunwrapAndIncrement(result:Result<number>):number{returnresult.unwrap()+1}unwrapAndIncrement(Ok.new(0))// will return 1unwrapAndIncrement(Err.error("Error")))// will throw Error("Error")

Optional

Useok() anderr() to get an Option, and useinner to get the inner value ifSome, orundefined ifNone

functionmaybeSlice(result:Result<string>):string|undefined{returnresult.ok().inner?.slice(0,5)}maybeSlice(newOk("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

import{Result,Ok,Err}from"@hazae41/result"functiontryIncrement(result:Result<number,Error>):Result<number,Error>{returnresult.mapSync(x=>x+1)}tryIncrement(newOk(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

import{Result,Ok,Err}from"@hazae41/result"functionincrementOrFail(result:Result<number,Error>):number|Error{if(result.isOk())result// Ok<number>elseresult// Err<Error>}

Wrapping

You can easily wrap try-catch patterns, with support for async and sync

constresult=Result.runAndWrapSync(()=>{if(something)return12345elsethrownewError("It failed")})

Rewrapping

If another library implements its own Result type, as long as it hasunwrap(), you can rewrap it to this library in one function

interfaceOtherResult<T>{unwrap():T}functionrewrapAndIncrement(other:OtherResult<number>):Result<number>{returnResult.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

try{returnnewOk(doSomethingThatThrows())}catch(e:unknown){returnnewErr(easError)}

But, sometimes, you want to do a bunch of actions, unwrap everything, catch everyting and return Err

/** * BAD EXAMPLE **/try{constx=tryDoSomething().unwrap()consty=tryDoSomething().unwrap()constz=tryDoSomething().unwrap()returnnewOk(doSomethingThatThrows(x,y,z))}catch(e:unknown){returnnewErr(easError)}

But what if you only want to catch errors thrown fromErr.unwrap(), and not errors coming fromdoSomethingThatThrows()?

You can do so by usingResult.unthrow(), it will do a try-catch but only catch errors coming fromErr.throw()

returnResult.unthrowSync<void,Error>(t=>{constx=tryDoSomething().throw(t)consty=tryDoSomething().throw(t)constz=tryDoSomething().throw(t)returnnewOk(doSomethingThatThrows(x,y,z))})

[8]ページ先頭

©2009-2025 Movatter.jp