- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
on 10-04-2019 09:19 AM
This post is a high level introduction to an alternative strategy that replaces the conventional try/catch implementation by isolating its logic into a single purpose function. The demo implementation will use strategies and theory from previous posts (DRY, Piping) by adding a try/catch logic into a pipeline.
It will be left up to the reader to fully mature it to reach benefits such as reduced code noise, single entry point for error handling, greater scalability, readability, reader comprehension targeted at only the logic, along other benefits.
I will not delve into it's theory outside explaining the behavior. Hopefully it is of use and can find it's way into more projects.
The Goal:
Introduce a functional try/catch to be used in a pipeline that allows execution of a failure or success function based on whether an exception occurred or not. In All, replace embedded try/catch.
Replace unnecessary try/catch:
function plusOne (i) {
try {
return i + 1;
} catch (error) {
gs.debug(error);
}
return i;
}
function plusTen (i) {
try {
return i + 10;
} catch (error) {
gs.debug(error);
}
return i;
}
function triggerAnError (i) {
try {
if (i > 100) {
throw new Error('this is an error');
}
return i;
} catch (error) {
gs.debug(error);
}
return i;
}
The Strategy:
- Introduce a try/catch function
- Create a Failure and Success polymorphic objects
- Utility function that builds a safePipe
The try/catch function
var tryCatch = function tryCatch (morphism) {
return function tryCatchApplyMorphism (value) {
try {
return Success(morphism(value));
} catch (error) {
return Failure(error);
}
};
};
The curried tryCatch function accepts a morphism (a function) and returns another function that itself accepts an argument to be executed by morphism. If an exception occurs upon execution the catch returns a Failure object, otherwise a Success object is returned. This function then becomes the single entry point for all exception handling.
The Failure and Success Objects
var Success = function Success (value) {
return {execute: execute, thisOrThat: thisOrThat, type: 'Success'};
/************************************************************ */
function execute (morphism) {
return Success(morphism(value));
}
//the function to execute when an action is to be taken from the try and catch
function thisOrThat (failureMorphism, successMorphism){
return Success(successMorphism(value));
}
};
var Failure = function Failure (value) {
return { execute: execute, thisOrThat: thisOrThat, type: 'Failure'};
function execute (morphism) { //executes a function against value
return Failure(value);
}
//the function to execute when an action is to be taken from the try and catch
function thisOrThat (failureMorphism, successMorphism) {
Failure(failureMorphism(value));
}
};
The Failure and Success objects use polymorphism to create containers with uniform api from which to seamlessly interact between success or failure without having to identify the type and still be able to further execute more functions against the value.
The safePipe Utility function for a common and less cluttered entry point
function safePipe (functions) {
return tryCatch(pipe(functions));
}
safePipe does nothing more than wrap the result of pipe function with the new tryCatch function and return it.
The cleaned up logic to be executed in the pipeline
function plusOne (i) {
return i + 1;
}
function plusTen (i) {
return i + 10;
}
function triggerAnError(i) {
if (i > 100) {
throw new Error('this is an error');
}
return i;
}
function reactToError (err) {
gs.debug('i reacted to an error ' + err);
return err;
}
function doSomethingOnSuccess (value) {
gs.debug('do something nice with ' + value);
return value;
}
function doSomethingElse (value) {
gs.debug('doing something else' + value);
return value;
}
plusOne, plusTen, and triggerAnError are the functions that will build executed one after the other one like a pipeline.
reactToError is the function to be executed when an exception is thrown
doSomethingOnSuccess is the function to execute when there is no error
doSomethingElse is a function to execute after an error or not (note that because of polymorphism, an function execution after an error will not trigger anything and will simply fall through without side effects.
Tying it all together
1 .Test with error
var tryAddition = safePipe([plusOne,plusError,plusTen]);
tryAddition(1100).thisOrThat(reactToError, doSomethingOnSuccess)
.execute(doSomethingElse);
the 1100 value passed into tryAddition will trigger an error and it will be handled by reactToError
*** OUTPUT ***
i reacted to an errorError: this is an error (sys.scripts extended logging)
2. Test with an error
var tryAddition = safePipe([plusOne,plusError,plusTen]);
tryAddition(10).thisOrThat(reactToError, doSomethingOnSuccess)
.execute(doSomethingElse);
the 10 value passed into tryAddtion will not trigger an error, therefore being handled by doSomethingOnSuccess
*** OUTPUT ***
do something nice with 21 (sys.scripts extended logging)
doing something else 21 (sys.scripts extended logging)
Why it works:
- Polymorphism
- Curried Functions
- Passing functions as arguments
Different implementations of execute by Failure and Success allow errors to flow down a different path than otherwise error-less code does. On Failure errors simply fall through to the end of the chained execute calls without actually running the functions. Success, on the other hand executes each passed in function.
Functional Programming
If you are interested in fully understanding this approach, do a search on Functional Error Handling
- 1,323 Views