had
Results generator tool for returning objects which contain either a success result or an error result.
It's possible to use this for every function return result. However, I think it's most appropriate at boundaries.
For example, when returning a result from a library's public API. The boundary is where values leave a library's control. It may also be helpful in important or significant functions. There are plenty of "little functions" to simply return the straight value.
However, if this was used everywhere, the insight and control of return results would be considerable. It can build up a history of return results which allows an insight into an error which was passed up multiple times.
Also, the information about an error is much more specific when it's used to specify exactly what's going on in the code at the place the error result is generated. It can say which array variable, which index, what it is trying to do with it, and more.
The main thing to keep in mind is this means a function will always return an object. That object is an error if it has an error
property. Otherwise, it's a success result, and may have a value
property containing the success result value.
For more explanation, read Why had?
Note, v0.7.0 includes significant changes due to a rewrite.
Quick Start
Install
npm install had --save
Require + Create
var had = id:'name your had' // OR:var buildHad = had =
Return success or error
// provide the object you want stored in the `error` property// of the error result object.// if you don't provide one then `'unknown'` will be used.return had // provide the object you want stored in the `value` property// of the success result object.// if you don't provide one then no `value` property// is set on the result object.return had
Use success or error result
// receive a result from a function which provides// `had` result objects.var result = // test if it is a successful result or an error.// NOTE: do this yourself by check for `error` property.if had // success, so, use `result.value` else // return the error result, but, // wrap it with our had. return had // or, provide our own error info about // receiving this error here. // NOTE: we provide the error result we just // received first so it is part of the error // result we return as a `previous` result. return had // OR:// check for error property:if resulterror // return the error result instead of wrapping // it with your own had.error() call. return result else // no error, so, use `result.value` console
Table of Contents
- Install
- Basic Use
- Require and create
had
- Success result example
- Error result example
- Null Argument Check
- Null Property Check
- Result check
- Combo
- Require and create
- Advanced Use
- What's in a Result Object
- API
- Why had?
- Future Plans
- MIT License
Basic Use
Require
Require had
and call its function with options to create an instance.
var had = id:'name your had' // OR:var buildHad = had =
Success
See had.success(value) for full example.
{ return had} var result = console
Error
See had.error(value) for full example.
{ return had} var result = console
Null argument check
See had.nullArg(options) for full example.
if had return had // already contains the error info
Null property check
See had.nullProp(options) for full example.
var object = some: 'thing' another: null if had // would already contain the error info return had // returns true when property:// 1. doesn't exist// 2. is null// 3. is undefinedif had // would already contain the error info. return had // NOTE:// because it builds up results it's possible to run// nullProp() and nullArg() multiple times and// then return had.results() which will contain all the// error results.
Result check
See had.isSuccess(result) for full example.
var result = if ! had return had// else it has your success result, use them // OR:if resulterror // handle the error or pass it up via return else // use the success result value
Combo
Above sections are simple "this is how to call it" examples. Below is a more complete example using all of the "basic usage" functions.
var had = id:'sum' { var type type = typeof num if had // returns the null arg error result return had else if type != 'number' return had else // return a simple success result (empty, no value) return had } { var sum result result = if resulterror // include error result and our added info return had result = if resulterror // include error result and our added info return had sum = num1 + num2 return had} var result = // this is what the result is like.// contains both errors.result = had: 'sum' error: message: 'invalid num2' previous: had: 'sum' error: message: 'number required' type : 'typeof' is :'string' value : 'abc' if had console else // handle the `result.error` info result = // this is the success result:result = had: 'sum' value: sum: 33 previous: null if resulterror // handle error result else console
Advanced Use
Had Result ?
Check a result to see if it was provided by a had
instance?
I haven't implemented this specifically because I haven't seen a use for it yet.
Instead, what I do is check for a falsey value, or had
error, and return an error including the result.
// receive a result from something using hadvar result = // check if it's a success resultif ! had // when it's not, include it in the error result. return had
See directly below for explanation of how the result is included.
Include other results
You may combine a had
result you received from elsewhere into your own results.
See Had Result ? about testing if an object is a had
result.
(also shown directly above this section.)
var result = return had// (bad examples)
That's it. If it's a had
result it will be included in your error result.
Note: When it's instead a falsey value (false, null, undefined, empty string) then only the error info you provide is used to create your error.
What's in a Result Object
Single Success Result
var had = id:'success' result // it's okay to call success() w/out a value.result = had// it looks like this:result = had: 'success' previous: null // or, provide anything you want for the valueresult = had// looks like this:result = had: 'success' value: key1:'value1' key2:'value2' previous: null
Single Error Result
var had = id:'error' result = had // it looks like this:result = had: 'error' error: message:'something bad' previous: null
Multiple Results
Multiple Success Results
When a second result is added it becomes the current return result and stores the previous result.
This pattern continues with each subsequent result added until the result is finally pulled out for a return.
How to pull out all stored return results:
had.results()
- uses the most recent result as the main return result and all others are in an array set into propertyprevious
.had.error()
andhad.success()
- they will gather all results and return them. These accept a single result to use for the most recent result, and, they can accept two arguments with the first one a "had result" from another call to include as well.
It doesn't matter whether results are success or error results. The most recent result is what's returned to result
and the previous results are in an array set into previous
property on the result.
var had = id:'multi' result // this stores a success result into the `had` had // this creates a new success result and puts the previous one// into `previous` property in an array.result = hadresult = // contents of result had: 'multi' value: something:'else' second:true previous: // array order is first element is the *oldest* result // second most recent result is last in the array // (most recent is the one set into `result` above) had: 'multi' value: some:'thing' first:true // no previous property on results in the `previous` array.
Multiple Error Results
var had = id:'multi' result had result = hadresult = // contents of result had: 'multi' error: message:'reconnect denied' type :'network' reason:'invalid token' previous: message:'unexpected disconnect' type :'network' id:737526355
Some mixed results:
var had = id:'mixed' result hadhadhadhad// use had.results() to get current results.// or,use had.success() instead of had.addSuccess() above.var results = hadresults = // the last result, a success had: 'mixed' value: something: 'else' previous: // the first result, an error had: 'mixed' error: message:'someError' type:'theType' input:123 // the second result, a success had: 'mixed' value: some:'thing' // the third result, an error had: 'mixed' error: message:'someError' type:'theType' input:456
API
had.success(value)
Does:
- creates new success result with
value
. See Success Results - if previous results exist, they are included in an array in the
previous
property. See Multiple Results - clears the
had
so it's ready for new results - returns the results
var had = id:'success' result = had result = // contents of result are: had: 'success' value: key1: 'value1' key2: 'value2' // previous results would be in an array here. // ordered oldest to newest previous: null
Note
Two arguments are used when including another result into this one. When both arguments are provided it is assumed the first one is the result to include, and the second is your success value. When the first argument is a simple (non-had) value then it isn't used.
result = // if result is a "had result" then it is included as a// previous result and then the second argument is used// to make the main return result.had
had.error(value)
Does:
- creates new error result from error. See Error Results
- if previous results exist, they are included in an array in the
previous
property. See Multiple Results - clears the
had
so it's ready for new results - returns the results
var had = id:'error' result = hadresult = // contents of result are: had: 'error' error: message:'null' type: 'param' name: 'someParam' // previous results would be in an array here. // ordered oldest to newest previous: null
Note
Two arguments are used when including another result into this one. When both arguments are provided it is assumed the first one is the result to include, and the second is your error. When the first argument is a simple (non-had) value then it isn't used.
result = // if result is a "had result" then it is included as a// previous result and then the second argument is used// to make the main return result.had
had.results(options)
Return the results stored in had
.
Does:
- clears the
had
so it's ready for new results - returns the results
- if there are no results then it returns
{ had: 'its id' }
- I didn't have a use for the
options
argument so it's currently copying its properties into the result object so it's possible to override the result info.
var had = id:'results' result = had // result = true, it was added// now call results() to get that objectresult = hadresult = // contents of result are: had: 'results' error: message: 'null' type: 'param' name: 'someParam' previous: null
had.addSuccess(value)
Stores a Success result from value and does not return the results. The info is held until one of these are called:
Does:
- creates new error result from value. See Error Results
- if previous results exist, they are included in an array in the
previous
property. See Multiple Results
var had = id:'add' result had result = hadresult = // contents of results are: had: 'add' value: file:'anotherFile' note:'file created' previous: had: 'add' value: file:'someFile' note:'file already existed'
had.addError(value)
Stores an Error result from options and does not return the results. The info is held until one of these calls:
Does:
- creates new error result from value. See Error Results
- if previous results exist, they are included in an array in the
previous
property. See Multiple Results
var had = id:'add' result had result = hadresult = // contents of result are: had: 'add' error: message:'delete file failed' type:'fs' file:'fileName' previous: had: 'add' error: message:'path is a directory' type:'fs' file:'fileName'
had.nullArg(argName, arg)
Returns true if arg
is null or undefined; false otherwise.
var had = id:'arg' someArg = 1234 result if had // result = false, so the if won't run someArg = nullif had // it is null so this if does run. // what the results() looks like right now: result = had result = // contents of results are: had: 'arg' error: message: 'null' type: 'arg' name: 'someArg' previous: null
had.nullProps(key, object)
Returns true if the object
is null or undefined, or, if the key
in object
is null or undefined; false otherwise.
var had = id:'prop' object1 = some: 'thing' object2 = some: null object3 = null if had // result = false, so the if won't run if had // it is null so this if does run. // what the results() looks like right now: result = had result = // contents of results are: had: 'prop' error: message: 'null' type: 'prop' name: 'some' // NOTE: // no `object` property cuz // the object wasn't null, the prop was. previous: null if had // the object itself is null // so, this if will run. // what the results() looks like right now: result = had result = // contents of results are: had: 'prop' error: message: 'null' type: 'prop' name: 'some' // NOTE: // this means the object itself was null. object: true previous: null
had.isSuccess(result)
Returns true when result
is a had
success result with no error property or when a truthy value; otherwise, false.
// any value, might be a `had` result, might notvar result = // if not a truthy or a `had` success resultif ! had // return an error containing other result had
Why had?
I've had enough of throwing errors when a common problem occurs and writing try-catch statements to handle errors thrown by other's. They are often poorly descriptive of the issue. In JavaScript they are often swallowed by some layer of an application. And, try-catch blocks aren't optimized by V8.
Avoid throwing/catching errors by always returning a result object. Put errors into the error
property and put success values into value
property. Then, test for error by checking if the error
property exists.
Then, when errors occur, they can returned using had
to format them. Then we're using normal flow control with no throw involved; no exception. All the code using had
for its results should check if there's an error and do something different when there is.
This causes:
-
the programmer may provide helpful error information into an error object instead of throwing an Error with a general message. This aids in debugging, of course. It also encourages the programmer to spend time really thinking about what's happening and what information someone else will want to know about the error at that point in the source code.
-
the programmer calling the code can know ahead of time what detailed information they'll receive when an error is returned. No testing strings in Error messages.
-
develop a standard error property usage pattern, such as a
type
, and corresponding info, then, you can write reusable error handling tools which make writing error handling simpler. -
no stepping outside the normal program flow to fly up through layers until a try-catch is encountered. That has use for errors representing system failures which must bail up to a handler. All those other errors? Let's manage them directly. Instead, require the caller directly above the error occurrence to inspect the return results, discover the error, and respond. We can include a lot of information rarely included in thrown errors. It's possible to still pass-the-buck up by returning received error results directly or by including it in a new call to
had.error()
to add your error info on top of the previous one. Then, the receiver above gets both results. -
changes the perception from always expecting return results to be a success value to expecting the return result to either have the success value in
result.value
or an error inresult.error
.
Future Plans
There are more checks I could add, such as one which helps test arrays.
The current API primarily expects synchronous usage. It's possible to generate the results and pass them to a callback instead of returning them. In which case, the callbacks would need to shift from expecting two args like (error, success)
to expecting a single arg containing the result like (result)
.
Then, instead of doing:
{ if error // handle the error return // handle the `result`}
We'd have callbacks like:
{ if resulterror // handle the error return // handle the result: `result.value`}
Although the above is possible I'm sure I could do more to support asynchronous code.
For example, provide an adaptor which wraps traditional callbacks to accept a had result object and then call the traditional callback with (result.error, result.value)
.
Register Handlers
I have an idea to allow registering functions on a had
which run for specific
error
or type
values. Could let the functions look at the object and decide
if they will do anything or not, or, use a strategy pattern to select them.
Then, if someone wanted to change a library to throw Errors instead, (hey, some persons really like thrown errors), they can register a handler which does exactly that.
Or, it could alter an error object, or log it, or report it to another tool, or send info to a developer-mode tool, or grab extra info from a service, or prevent the error from being used (maybe a library is sending something as an error and the library user disagrees).
There are a lot of possibilities.