Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
June 9, 2019 12:30 pm GMT

Result: Composition and Error handling

We can improve our error handling and composition by leveraging a Result class and several other tools from the functional programming world.

Instead of throwing errors, we wrap our results. Either the Result is an Error value, or a Success value, in the process documenting the possible errors. Callers must first examine and unwrap the Result, handling either the Success or Failure case. Paving the way for more functional programming and composition.

For a more complete introduction to the Result class and Railway Oriented Programming:

In these series I will share my findings during my (exciting) journey.

Imperative sample

const r = doSomeAction() // Result<string, SomeVariableIsInvalid | ServiceUnavailableError>if (r.isErr()) { // r: Error<SomeVariableIsInvalid | ServiceUnavailableError>  if (r.error instanceof SomeVariableIsInvalid) {    ctx.body = r.error.message    ctx.statusCode = 400  } else {    ctx.statusCode = 500  }  return}// r: Ok<string>ctx.body = r.valuectx.statusCode = 200

doSomeAction could be implemented like:

function doSomeAction(): Result<string, SomeVariableIsInvalid | ServiceUnavailableError> {  if (!someVariableIsValid) {    return err(new SomeVariableIsInvalid("some variable is not valid")  }  if (!isServiceAvailable()) {    return err(new ServiceUnavailableError("The service is currently unavailable")  }  return ok("success response")}

Functional sample

doSomeAction() // Result<string, SomeVariableIsInvalid | ServiceUnavailableError>  .map(value => {    ctx.body = value    ctx.statusCode = 200  })  .mapErr(error => {    if (error instanceof SomeVariableIsInvalid) {      ctx.body = error.message      ctx.statusCode = 400    } else {      ctx.statusCode = 500    }  })

All "operators" must live on the Result object and thus extension is harder. (This is similar to how for instance RxJS started)

Functional Composition

doSomeAction() // Result<string, SomeVariableIsInvalid | ServiceUnavailableError>  .pipe(    map(value => {      ctx.body = value      ctx.statusCode = 200    }),    mapErr(error => {      if (error instanceof SomeVariableIsInvalid) {        ctx.body = error.message        ctx.statusCode = 400      } else {        ctx.statusCode = 500      }    })  )

The operators are now just functions, easy to extend and roll our own ;-) (RxJS v5.5 users may see some similarities here)

Data last

const pipeline = pipe(  map(value => {    ctx.body = value    ctx.statusCode = 200  }),  mapErr(error => {    if (error instanceof SomeVariableIsInvalid) {      ctx.body = error.message      ctx.statusCode = 400    } else {      ctx.statusCode = 500    }  }))pipeline(doSomeAction())

So pipeline is now reusable. If only tc39 proposal-pipeline-operator would land soon, so that we get syntactic sugar that will hide some boiler plate and syntactic noise :)

Building on top

Further decomposition into separate functions, so that they become re-usable, or to separate the levels of abstraction so that the pipeline becomes easier to read.

const writeSuccessResponse = value => {  ctx.body = value  ctx.statusCode = 200}const writeErrorResponse = error => {  if (error instanceof SomeVariableIsInvalid) {    ctx.body = error.message    ctx.statusCode = 400  } else {    ctx.statusCode = 500  }}const pipeline = pipe(  map(writeSuccessResponse),  mapErr(writeErrorResponse))

Further decomposition:

const writeSuccessResponse = value => {  ctx.body = value  ctx.statusCode = 200}const writeDefaultErrorResponse = error => {  ctx.statusCode = 500}const writeSomeVariableIsInvalidErrorResponse = error => {  if (error instanceof SomeVariableIsInvalid) {    ctx.body = error.message    ctx.statusCode = 400  }}const pipeline = pipe(  map(writeSuccessResponse),  mapErr(writeDefaultErrorResponse),  mapErr(writeSomeVariableIsInvalidErrorResponse),)

Perhaps another option:

const mapErrIf = (errorHandler: error => void, predicate: error => boolean) =>   error => {    if (!predicate(error)) { return }    errorHandler(error)  }}// usagemapErrIf(_ => ctx.statusCode = 400, error => error instanceOf SomeVariableIsInvalid)

And there are of course many other options and forms of composition, let that be the reader's excercise ;-)

Framework and Sample code

I'm working on an application framework while exploring these topics, that leverages the pipeline composition extensively, Sample app included!

Source code:

What's Next

Next in the series, I plan to introduce the more advanced concepts like flatMap, toTup, tee and others :)

Further reading

Be sure to also check out gcanti/fp-ts; a heavily functional programming oriented library, especially v2 is looking very promising due to similar pipe composition!


Original Link: https://dev.to/patroza/result-composition-and-error-handling-m8i

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To