Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Alexey Tukalo
Alexey Tukalo

Posted on

     

Other tools for Monadic error handling

In the previous article we already gained some intuition regarding monadic error handling withPromise, it is time for us to move forward. JavaScript doesn't have native solutions for monadic error handling beyondPromise, but there are many libraries which helps to fulfil the functionality.amonad has the most similar to thePromise API. Therefore it is going to be used for the following examples.

Abstraction which represents the result of computations which can possibly fail is commonly known asResult. It is like immediately resolvedPromise. It can be represented by two values:Success contains expected information, whileFailure has the reason for the error. Moreover, there isMaybe as known asOption which also embodied by two kinds:Just andNone. The first one works in the same way asSuccess. The second one is not even able to carry information about the reason for value's absents. It is just a placeholder indicated missing data.

Creation

Maybe andResult values can be instantiated via factory functions. Different ways to create them are presented in the following code snippet.

constjust=Just(3.14159265)constnone=None<number>()constsuccess=Success<string,Error>("Iron Man")constfailure:Failure<string,Error>=Failure(newError("Does not exist."))
Enter fullscreen modeExit fullscreen mode

NaN safe division function can be created using this library in the way demonstrated below. In that way, the possibility of error is embedded in the return value.

constdivide=(numerator:number,quotient:number):Result<number,string>=>quotient!==0?Success(numerator/quotient):Failure("It is not possible to divide by 0")
Enter fullscreen modeExit fullscreen mode

Data handling

Similarly toPromise,Result andMaybe also havethen(). It also accepts two callback: one for operations over enclosed value and other one dedicated for error handling. The method returns a new container with values processed by provided callbacks. The callbacks can return a modified value of arbitrary type or arbitrary type inside of similar kind of wrapper.

// converts number value to stringconsteNumberStr:Maybe<string>=Just(2.7182818284).then(eNumber=>`E number is:${eNumber}`)// checks if string is valid and turns the monad to None if notconstvalidValue=Just<string>(inputStr).then(str=>isValid(inputStr)?str:None<string>())
Enter fullscreen modeExit fullscreen mode

Besides that due to the inability of dealing with asynchronism, availability of enclosed value is instantly known. Therefore, it can checked byisJust() andisSuccess() methods.

Moreover, the API can be extended by a number methods to unwrap a value:get(),getOrElse() andgetOrThrow().get() output is a union type of the value type and the error one forResult and the union type of the value andundefined forMaybe.

// it is also possible to write it via isJust(maybe)if(maybe.isJust()){// return the value hereconstvalue=maybe.get();// Some other actions...}else{// it does not make sense to call get()// here since the output is going to be undefined// Some other actions...}
Enter fullscreen modeExit fullscreen mode
// it is also possible to write it via isSuccess(result)if(result.isSuccess()){// return the value hereconstvalue=result.get();// Some other actions...}else{// return the error hereconsterror=result.get();// Some other actions...}
Enter fullscreen modeExit fullscreen mode

Error handling

The second argument of thethen() method is a callback responsible for the handling of unexpected behaviour. It works a bit differently forResult andMaybe.

In the case ofNone, it has no value, that's why its callback doesn't have an argument. Additionally, it doesn't accept mapping to the deal, since it should produce anotherNone which also cannot contain any data. Although, it can be recovered by returning some fallback value inside ofMaybe.

In the case ofFailure, the second handler works a bit similar to the first one. It accepts two kinds of output values: the value of Throwable as well as anything wrapped byResult.

Additionally, both of them are also capable of handling callbacks returning avoid, it can be utilized to perform some side effect, for example, logging.

// tries to divide number e by n,// recoveries to Infinity if division is not possibleconsteDividedByN:Failure<string,string>=divide(2.7182818284,n).then(eNumber=>`E number divided by n is:${eNumber}`,error=>Success(Infinity))
Enter fullscreen modeExit fullscreen mode
// looks up color from a dictionary by key,// if color is not available falls back to blackconstvalueFrom=colorDictionary.get(key).then(undefined,()=>"#000000")
Enter fullscreen modeExit fullscreen mode

Similarly to previous situations, it is also possible to verify if the value isFailure orNone viaisNone() andisFailure() methods.

// it is also possible to write it via isNone(maybe)if(maybe.isNone()){// it does not make sense to call get()// here since the output is going to be undefined// Some other actions...}else{// return the value hereconstvalue=maybe.get();// Some other actions...}
Enter fullscreen modeExit fullscreen mode
// it is also possible to write it via isFailure(result)if(result.isFailure()){// return the error hereconsterror=result.get();// Some other actions...}else{// return the value hereconstvalue=result.get();// Some other actions...}
Enter fullscreen modeExit fullscreen mode

Which one should be used?

Typical usage ofMaybe andResult is very similar. Sometimes it is hardly possible to make a choice, but as it was already mentioned there is a clear semantic difference in their meanings.

Maybe, primary, should represent values which might not be available by design. The most obvious example is the return type ofDictionary:

interfaceDictionary<K,V>{set(key:K,value:V):voidget(key:K):Maybe<V>}
Enter fullscreen modeExit fullscreen mode

It can also be used as a representation of optional value. The following example shows the way to model aUser type withMaybe. Some nationalities have a second name as an essential part of their identity others not. Therefore the value can nicely be treated asMaybe<string>.

interfaceClient{name:stringsecondName:Maybe<string>lastName:string}
Enter fullscreen modeExit fullscreen mode

The approach will enable implementation of client's formatting as a string the following way.

classVIPClient{// some implementationtoString(){return"VIP:"+this.name+// returns second name surrounded// by spaces or just a spacethis.secondName.then(secondName=>`${secondName} `).getOrElse("")+this.lastName}}
Enter fullscreen modeExit fullscreen mode

Computations which might fail due to obvious reason are also a good application forMaybe. Lowest common denominator might be unavailable. That is why the signature makes perfect sense forgetLCD() function:

getLCD(num1:number,num2:number):Maybe<number>
Enter fullscreen modeExit fullscreen mode

Result is mainly used for the representation of value which might be unavailable for multiple reasons or for tagging of a data which absents can significantly affect execution flow.

For example, some piece of class’s state, required for computation, might be configured via an input provided during life-circle of the object. In this case, the default status of the property can be represented byFailure which would clarify, that computation is not possible until the state is not initialized. Following example demonstrates the described scenario. The method will return the result of the calculation asSuccess or “Data is not initialized” error message asFailure.

classResultExample{value:Result<Value,string>=Failure(Dataisnotinitialized)init(value:Value){this.value=Success(value)}calculateSomethingBasedOnValue(){returnthis.value.then(value=>someValueBasedComputation(value,otherArgs))}}
Enter fullscreen modeExit fullscreen mode

Moreover,Result can replace exceptions as the primary solution for error propagation. Following example presents a possible type signature for a parsing function which utilizesResult as a return type.

parseUser(str:string):Result<Data>
Enter fullscreen modeExit fullscreen mode

The output of such a function might contain processed value asSuccess or an explanation of an error asFailure.

Conclusion

Promise,Result andMaybe are three examples of monadic containers capable of handling missing data.Maybe is the most simple one, it is able to represent a missing value.Result is also capable to tag a missing value with an error message.Promise naturally extends them with an ability to represent data which might become available later. Moreover, it can never become available at all. That might happen due to error which can be specifically passed in case of rejection. So,Promise is the superior one and it can basically model all of them. However, specificity helps to be more expressive and efficient.

This approach to error handling is a paradigm shift since it prevents engineers from treating errors as exceptional situations. It helps to express them as an essential part of the execution. You know, from time to time all of us fails. So in my mind, it is wise to follow a known principle: "If you are going to fail, fail fast".

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Language agnostic software developer interested in functional programming, software design, web development.
  • Location
    Munich
  • Education
    Savonia University of Applied Science
  • Work
    Full-Stack Software Engineer at Cartken
  • Joined

More fromAlexey Tukalo

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp