Errors Get Propagated
Understand js errors.
Understand how errors can be caught.
Understand how promises that throw errors can be interacted with.
Propagation
Errors can get "bubbled up". Here's an example, with a "walkthrough" below:
const PRIMARY_COLORS = ['red', 'yellow', 'blue'];
const NOT_MIXABLE_ERR = 'ERR_MUST_BE_MIXBLE';
const NOT_PRIMARY_ERR = 'ERR_MUST_BE_PRIMARY';
const mixedColors = {
red: {
blue: 'purple',
yellow: 'orange',
},
blue: {
red: 'purple',
yellow: 'green',
},
yellow: {
blue: 'green',
red: 'orange',
},
};
class PrimaryError extends Error {
constructor (colorParam = '') {
super(colorParam + ' must be a primary color')
}
get name () { return 'PrimaryError' }
get code () { return NOT_PRIMARY_ERR; }
}
class CannotMixError extends Error {
constructor(a,b,reason) {
super(`cannot mix ${a} and ${b}: ${reason}`);
}
get name() {
return 'CannotMixError';
}
get code() {
return NOT_MIXABLE_ERR;
}
}
function isValidPrimary(color) {
if (!PRIMARY_COLORS.includes(color)) {
throw new PrimaryError(color)
}
return true
}
function mixPrimaries(a, b) {
try {
isValidPrimary(a);
isValidPrimary(b);
return mixedColors[a][b];
} catch (error) {
throw new CannotMixError(a,b, error.message)
}
}
function runIt(){
const mixedRes = mixPrimaries('red', 'pink')
console.log(`mixedResult : ${mixedRes}`)
}
runIt()
Breaking Down The Error Propagation Example
Thinking Through The Propagation
One way of looking at the order of operations above:
runIt
gets calledrunIt
runsmixPrimaries
&& passes 2 argsmixPrimaries
- calls
isValidPrimary
up to 2x, 1x per arg - returns the result of a nested object, essentially "mixing" the two primary colors
- calls
Considering the errors:
runIt
is a function that gets called/ran at the bottom of the coderunIt
callsmixPrimaries
, which is a function declared earlier in the file- the two args passed above to the fn are
red
andpink
pink
is not a primary color, and the code should (does) not allowed this
- the two args passed above to the fn are
mixPrimaries
...- checks each parameter for validity with
isValidPrimary
isValidPrimary
- either returns true or
- throws an instance of a custom-made
PrimaryError
- an error gets thrown here
- checks each parameter for validity with
A ply-by-play of the error & how it relates:
- the first parameter of the
mixPrimaries
fn,red
, is a valid primary - the second,
pink
, is not - this causes an error to get thrown in
isValidPrimary
- this error string reads
pink must be a primary color
as described by the custom errorPrimaryError
- the error gets "caught" inside the
catch
block ofmixPrimaries
mixPrimaries
takes the error and "uses" theerror.message
by passing the error.message, along with the 2 params passed to itself, to another custom error calledCannotMixError
- the
CannotMixError
is not "caught" by anycatch
block, so the node process receives the error and throws to stdout:
throw new CannotMixError(a,b, error.message)
^
CannotMixError: cannot mix red and pink: pink must be a primary color
at mixPrimaries (<path-to-file>/index.js:434:11)
at runIt (<path-to-file>/index.js:439:20)
at Object.<anonymous> (<path-to-file>/index.js:444:1)
at Module._compile (node:internal/modules/cjs/loader:1254:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1308:10)
at Module.load (node:internal/modules/cjs/loader:1117:32)
at Module._load (node:internal/modules/cjs/loader:958:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
at node:internal/main/run_main_module:23:47
Making use of the error output
The error output is helpful for several reasons:
- the name of the error is written frist, the
CannotMixError
- The "reason" of the error is second:
cannot mix red and pink: pink must be a primary color
- the code liness that interact with the error:
at mixPrimaries (...434:11)
shows that themixPrimaries
fn, at line434
character11
is a code "touchpoint" of the errorat runIt (...434:11)
shows that therunIt
fn, at line439
character20
is a code "touchpoint" of the errorat Object.<anonymous> (...434:11)
shows that theObject.<anonymous>
fn, which in our case is therunIt()
being called, at line444
character1
is a code "touchpoint" of the error- the notes beyond that reveals some node "inner workings" that interact with the error, which is beyond the scope of this post!
Move The Error "Up"
In the above example, the error gets thrown at mixPrimaries
and node throws the error.
The error can be moved "up", propagated, to the parent runIt
funcitonality.
Only a few things can change to "handle" the error so that an error is logged and not thrown:
- A value will be returned from
mixPrimaries
, "passed along" to therunIt
function runIt
will get updated- it will become an
async
function, to introduce the ability to catch an error - it will use the
().then().catch()
syntax to catch the error thrown inmixPrimaries
- it will become an
const PRIMARY_COLORS = ['red', 'yellow', 'blue'];
const NOT_MIXABLE_ERR = 'ERR_MUST_BE_MIXBLE';
const NOT_PRIMARY_ERR = 'ERR_MUST_BE_PRIMARY';
const mixedColors = {
red: {
blue: 'purple',
yellow: 'orange',
},
blue: {
red: 'purple',
yellow: 'green',
},
yellow: {
blue: 'green',
red: 'orange',
},
};
class PrimaryError extends Error {
constructor (colorParam = '') {
super(colorParam + ' must be a primary color')
}
get name () { return 'PrimaryError' }
get code () { return NOT_PRIMARY_ERR; }
}
class CannotMixError extends Error {
constructor(a,b,reason) {
super(`cannot mix ${a} and ${b}: ${reason}`);
}
get name() {
return 'CannotMixError';
}
get code() {
return NOT_MIXABLE_ERR;
}
}
function isValidPrimary(color) {
if (!PRIMARY_COLORS.includes(color)) {
throw new PrimaryError(color)
}
return true
}
function mixPrimaries(a, b) {
try {
isValidPrimary(a);
isValidPrimary(b);
if (a === b) throw new Error('must be different colors');
return mixedColors[a][b]
} catch (error) {
throw new CannotMixError(a,b, error.message)
}
}
async function runIt(){
try {
return mixPrimaries('red', 'red')
} catch (error) {
throw new Error(error)
}
}
runIt().then(d => console.log('runIt done: ',d)).catch(e => {
console.log('runIt error: ',e.message)
})
Make Meaningful Impact on the code
When code expects things and those things aren't met, developer-friendly code can be built-in to errors.
This will make exterminating bugs easier:
- understandable error messages give developers clear points to investigate
- line numbers point and filenames point exactly to where the error is written
- embracing the tradeoff of time-to-make error code vs. time-to-deliver usable code will pay for itself "down the road" by reducing the time it takes for a dev to find something that isn't working as expected. The time it takes to both creat error code and deliver meaningful functionality will both decrease over time.