I'm working in a large Javascript codebase at the moment littered with code resorting to Exceptions for flow control
function getChecklistUrl() {
try {
return dataLayerObject.config.checklist;
} catch (e) {
try {
console.error('dataLayer', e);
} catch (ignore) {}
}
}
I might favor conditional logic such as this implementation of the same function
function getChecklistUrl() {
if(typeof dataLayerObject == 'object' &&
'config' in dataLayerObject &&
typeof dataLayerObject.config == 'object' &&
'checklist' in dataLayerObject.config &&
typeof dataLayerObject.config.checklist == 'object') {
return dataLayerObject.config.checklist;
}
return null;
}
While the later feels longwinded, a helper function could certainly be written to reduce the boilerplate for such checks.
Is the former then idiomatic for Javascript? Is the later brittle (across browsers / scenarios) and better left to a try
/ catch
anyway? Or is the former simply evidence of laziness?
Edit
these objects are presumed to be 'plain' objects, such as var obj = {}
so I don't believe I care about the prototype chain here.
First of all, you don't need to check for both property in object
&& typeof obj[property] == 'object'
, you can only use the typeof
to take care of both checks. The reason is that, if that obj.property
doesn't exist, typeof
will return undefined
.
Therefore, you can modularize your approach by writing a small utility function that checks if something is an object:
function isObject(o) {
return typeof o == 'object' && o !== null; // take care of the corner case that typeof null == 'object'
}
Then just use it on your necessary object chain to find a property by checking that all of its owning objects exists:
function getChecklistUrl() {
if(isObject(dataLayerObject) &&
isObject(dataLayerObject.config) &&
isObject(dataLayerObject.config.checklist)) {
return dataLayerObject.config.checklist;
}
return null;
}
var dataLayerObject = {
config: {
checklist: ['array of stuff']
}
}
function isObject(o) {
return typeof o == 'object' && o !== null;
}
function getChecklistUrl() {
if (isObject(dataLayerObject) &&
isObject(dataLayerObject.config) &&
isObject(dataLayerObject.config.checklist)) {
return dataLayerObject.config.checklist;
}
return null;
}
console.log(getChecklistUrl()[0]);
This makes the code more organized and easier to read, IMHO.
You can also do something like a getter for your objects that take a dot-separated string and return you the property or null if the property doesn't exist:
function getObjProperty(obj, propString) {
if(!isObject(obj) || !propString || typeof propString != 'string') {
return null; // make sure obj is an object and propString is a non-empty string
}
var props = propString.split('.');
for (var i = 0, l = props.length; i < l; i++) {
var p = props[i];
if(!isObject(obj[p])) { return null; } // if current property isn't an object, return null
obj = obj[p]; // otherwise update the object to the next one down the property chain
}
return obj;
}
You would use it like: getObjProperty(dataLayerObject, 'config.checklist');
var dataLayerObject = {
config: {
checklist: ['array of stuff']
}
};
function isObject(o) {
return typeof o == 'object' && o !== null;
}
function getObjProperty(obj, propString) {
if (!isObject(obj) || !propString || typeof propString != 'string') {
return null;
}
var props = propString.split('.');
for (var i = 0, l = props.length; i < l; i++) {
var p = props[i];
if (!isObject(obj[p])) { //
return null;
} // if current property isn't an object, return null
obj = obj[p]; // otherwise update the object to the next one down the property chain
}
return obj;
}
console.log(getObjProperty(dataLayerObject, 'config.checklist'));
Or you can achieve this with a fairly straightforward recursive method
NOTE:
The examples above check the prototype chain as well. If you don't want this, you should use hasOwnProperty
when checking if a property is an object to also check that the property exists on the testing object.
Prefix's answer shows one variation of such an approach.