Write More Robust JavaScript: 7 Best Practices
January 15th, 2020
If you don’t know what a factory function is, it’s simply a function (that isn’t a class or constructor) that returns an object. This simple concept allows us to take advantage of JavaScript and its features to create powerful robust applications.
It’s important to know that they’re no longer factory functions when they’re called with the new
keyword.
Factory functions can be used to easily produce instances of objects without having anything to do with classes or the new keyword.
What it essentially means is that they ultimately become treated as just functions, which means they can be used to compose objects, functions, and even promises. This means you can mix and match factory functions together to create an enhanced factory function, then continue composing with other functions or objects to create even further enhanced ones. The possibilities are endless.
When we take that into consideration and combine it with good code practices, it really begins to shine.
Here is a simple example of a factory function:
function createFrog(name) {
const children = []
return {
addChild(frog) {
children.push(frog)
},
}
}
const mikeTheFrog = createFrog('mike')
When you’ve used factory functions enough, you begin to realize that compared to its class constructor counterpart, it promotes stronger reusability. This results in less code, an easier time refactoring since factory functions ultimately return arbitrary objects, and an easier time managing one code to another.
If you’re new to JavaScript, this section might be a little new to you as it was for me for the first two years of my experience with JavaScript.
(Keep in mind that this does not apply to classes because classes already attach methods onto their prototypes.)
Here’s an example of a constructor:
function Frog(name, gender) {
this.name = name
this.gender = gender
}
Frog.prototype.leap = function(feet) {
console.log(`Leaping ${feet}ft into the air`)
}
Why do this instead of directly attaching the leap method, like in the example below?
function Frog(name, gender) {
this.name = name
this.gender = gender
this.leap = function(feet) {
console.log(`Leaping ${feet}ft into the air`)
}
}
When we attach methods directly on the prototype, they get shared among all instances created by the constructor.
In other words, using the last example, if we created three separate Frogs (from this.leap = function() {...}
), then we end up creating three separate copies. This is a problem because the leap method will always stay the same and doesn't need to have its own copy to its instance.
Ultimately, this results in lower performance, when it could have been avoided. The this.name and this.gender properties need to be defined on the instance because in real life, frogs probably have their own names and gender so it makes sense to have them created on the instance level.
Here's an example of this approach used by the popular request package.
This practice works so well that it’s in extensive use today. If you’re a React developer, you’ve probably already been seeing this every day, especially when you’ve been working with Redux.
Using similar approaches also makes it extremely easy for you in your development flow since it even documents itself extremely well:
function createSpecies(type, name, gender) {
if (type === 'frog') {
return createFrog(name, gender)
} else if (type === 'human') {
return createHuman(name, gender)
} else if (type == undefined) {
throw new Error('Cannot create a species with an unknown type')
}
}
const myNewFrog = createSpecies('frog', 'sally', 'female')
TypeScript has become widely adopted in the JavaScript community due to its ability to provide a strong defense for type safety as well as its ability to help us catch bugs before they even occur.
Using TypeScript will enable your compiler to detect and show warnings about any potential errors in code before the code even runs.
But that’s not even close to a complete list of reasons why adopting TypeScript is good for any situation. One of the best things about TypeScript is that it allows you to use new features in JavaScript before they’re supported by major browsers since they get compiled down to earlier versions of JavaScript, ultimately being able to run in old browsers.
If you’re working on a project and plan to get serious with it, it’s almost a must to employ tests so that your app becomes more predictable, less error-prone, and resilient to changes in the future. In other words, if you plan to make your project future-proof, there’s no better way than to establish tests throughout your code. The more tests you put in your code, the more confidence you’ll have when it gets deployed in production to the real world.
The best part about tests? Just the fact that that they can help you catch bugs before bugs even get a chance to occur — wouldn’t anyone want that ability? I’m certain I do! That’s why I write unit tests in my projects.
You can get started with the types of tools that are in use today by reading this post on testing frameworks and tools to get you started.
As we all know, in JavaScript it’s obviously possible to have huge functions that do more than one thing.
When you’re new to programming, it might have felt like a good thing — I know I felt extremely good about myself when I wrote large pieces of code that worked. It basically meant the most to me and gave me lots of confidence to see any of my code run without problems, let alone when it was humongous blocks of code that I wrote. Boy oh boy, was I extremely naive back then!
If you want to write code that’s more maintainable, simple, and less error-prone, it’s best to try to keep it as simple and small as possible. The simpler it is, the easier it is to test individually.
This is especially relevant if you’re more fond of the functional programming paradigm. It’s common knowledge that functions should do one thing and must do it well.
try/catch
when using JSON.parse
or JSON.stringify
In JavaScript, when we pass JSON as input to the JSON.parse
method, it expects a properly formatted JSON as the first argument. If it's formatted incorrectly, it will throw a JSON parse error.
The danger coming from JSON parse errors is that receiving invalid JSON crashes your app. I’ve recently been in a situation at work where one of our web projects was failing because another internal package did not wrap a JSON.parse
in a try/catch. This ended up making a web page fail, and there was no way to get past this error unless the internal package fixed it. This happened because the JavaScript runtime was broken.
SyntaxError: Unexpected token } in JSON at position 107
You shouldn’t always expect valid JSON input as it can receive weird characters like the >
character, which is not uncommon today.
And that concludes this piece. I hope you found this to be valuable. Watch for more in the future!