Categories
JavaScript Best Practices

Maintainable JavaScript — Throwing Errors

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at ways to throw errors.

Errors

We throw errors when our program encounters some unexpected.

Maybe an incorrect value was passed into a function or operation.

Programming language defines rules and when we deviate from them, then errors should be thrown.

Debugging would be close to impossible if errors aren’t thrown and reported back to us.

If everything failed silently, then it’ll take a long time to find out what’s wrong with our program.

Errors are there to help us.

The problem we have is that errors may come up in unexpected places and times.

Default error messages may not give us enough information to solve the problem.

It’s always a good idea to plan for failure so that we can deal with them before they occur.

Throwing Errors in JavaScript

To throw errors in JavaScript, we use the throw keyword with an Error instance.

For instance, we can write:

throw new Error("an error occurred.")

We pass in a message that we want to display with the error.

The built-in Error type is available in all JavaScript implementation.

The constructor takes a single argument, which is the error message.

When an error is thrown and it isn’t caught by a try-catch block, then the browser displayed the message in the browser’s way.

Most browsers have a dev console to display errors in there.

An error we throw is treated the same as an error that we didn’t throw.

A bad way to use the throw keyword is to use it with something that isn’t an Error instance.

For instance, we may write something like:

throw "error";

This is bad because browsers may not respond the same way to this.

And also, we throw away useful information like the stack trace.

However, we can throw anything we like.

There’s nothing prohibiting this.

Firefox, Opera, and Chrome convert non-error objects to string when they’re thrown.

And IE doesn’t.

Advantages of Throwing Errors

Throwing our own error allows us to provide the exact text to be displayed by the browser.

Instead of line and column numbers, we can include any information we like that we’ll need to successfully debug the error.

For instance, if we have:

function getSpans(element) {  
  if (element && element.getElementsByTagName) {  
    return element.getElementsByTagName("span");  
  } else {  
    throw new Error("argument not a DOM element");  
  }  
}

Then we throw an error if an object isn’t a DOM element.

This way, we know that we didn’t pass in DOM elements if we see the error message.

With this setup, we see the error message that can help us debug what’s wrong with our code without looking too hard.

Conclusion

Throwing errors lets us know what’s wrong when we run into problems.

Without them, we won’t know what’s wrong when our code does something we don’t expect.

Categories
JavaScript Best Practices

Maintainable JavaScript — Storing Config Data

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at ways to store config data.

Storing Configuration Data

There’re various ways to store the config data of our app.

Front end frameworks like React, Vue, and Angular have their own ways of storing environment-specific config data.

The CLI programs that we use to create, run, and build the projects can read from them and get the right config values into our build artifacts.

For instance, with Create React App, we can create our own config file

REACT_APP_NOT_SECRET_CODE=abcdef

in our .env file.

Then we can read the value with process.env.REACT_APP_NOT_SECRET_CODE in our app’s code.

Then when we run npm start to start the app and npm run build to build the app, the value will be added.

It can take one file for each environment.

We can have:

  • .env: default config file.
  • .env.local: local overrides
  • .env.development, .env.test, .env.production: environment-specific settings
  • .env.development.local, .env.test.local, .env.production.local — local overrides.

We check ib .env and ignore the local ones.

As long as the key starts with REACT_APP_ , then it can be read.

Vue CLI also has something similar.

It takes the following files:

  • .env — default file
  • .env.local — local override
  • .env.[mode] — override for a specific environment
  • .env.[mode].local — local override for a specific environment

Then in the .env files, we can write:

VUE_APP_NOT_SECRET_CODE=some_value

As long as the prefix of the config key is VUE_APP_ , Vue CLI will read it.

For Node apps, we can use the dotenv package.

We can install it by running:

npm i dotenv

Then we can create our config by writing:

DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3

and then we can use it by writing:

require('dotenv').config()

Then we can read the config by writing:

const db = require('db')
db.connect({
  host: process.env.DB_HOST,
  username: process.env.DB_USER,
  password: process.env.DB_PASS
})

to get the config values from process.env .

We can also store the values in JSON format.

For instance, we can write:

{
  "MESSAGE_INVALID_VALUE": "Invalid value",
  "URL_INVALID": "/errors/invalid"
}

to store the JSON file.

Then we can read the JSON file with fetch , jQuery’s getJSON or whatever we want.

JSON can also be imported directly with import if we use a module bundler like Webpack, Rollup, or Parcel.

For instance, we can just write:

import config from './config.json'

to import our JSON file.

It’ll be imported as if it’s a regular JavaScript object and we can use them as such.

In all the examples, we store the config outside our app.

This way, we can just change the config file, and the values will be reflected everywhere that they’re referenced.

Conclusion

We can store configuration data externally so that we can change the value in one place and they’ll be reflected everywhere.

We can have separate config files for each environment to make our app work in all environments.

Categories
JavaScript Best Practices

Maintainable JavaScript — Separate HTML and JavaScript

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at loose coupling between JavaScript and HTML.

Keep HTML Out of JavaScript

We should keep our HTML code out of our JavaScript.

Having HTML in our JavaScript code make the app hard to debug when there’re any text or structural issues in our code.

It’s almost impossible to trace issues with dynamic HTML in our JavaScript code.

Therefore, we shouldn’t write code like:

const div = document.querySelector("div");
div.innerHTML = "<h3>Error</h3> <p>Invalid date.</p>";

Embedding HTML strings in our JavaScript is a bad practice.

It complicates tracking down any text or structural issues,

It’s hard to look through the DOM inspector in the dev console of our browser to fund the issue since it’s updated dynamically.

Therefore, it’s harder to work with than tracking down simple DOM manipulation issues.

The 2nd problem is maintainability.

If we need to change the text or markup, we’ve to change the HTML and the JavaScript code to change the text.

This is more work than changing them all in the HTML code.

The markup in JavaScript isn’t accessible for changes.

This is because we don’t expect HTML in our JavaScript code.

It’s much more error-prone to edit markup than it is to edit JavaScript since the markup is in string.

So by putting HTML in our JavaScript code, then we complicate our problem.

There are ways to insert HTML with JavaScript in a loosely coupled manner.

One way is to use some templating system like Handlebars.

For instance, we can write:

<script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>

<script>
  const template = Handlebars.compile("<p>{{firstname}} {{lastname}}</p>");

  console.log(template({
    firstname: "james",
    lastname: "smith"
  }));

</script>

We include the Handlebars library and then call Handlebars.compile to return a template function that lets us interpolate expressions in a template string.

If we want to use HTML with Handlebars, we add a special script tag:

<script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>

<script id="entry-template" type="text/x-handlebars-template">
  <div class="entry">
    <p>{{firstname}} {{lastname}}</p>
  </div>
</script>

Then we can add the following JavaScript to compile the template into HTML:

const source = $("#entry-template").html();
const template = Handlebars.compile(source);
const context = {
  firstname: "james",
  lastname: "smith"
}
const output = template(context);
$("body").append(output);

The script tag has the text/x-handlebars-template type to distinguish it from a JavaScript script tag.

So we can compile the template into HTML.

The compilation is done by getting the script with jQuery.

Then we call Handlebars.compile to compile the source.

The context has the variables we want to interpolate in our template.

Then we call template with the context to compile the data.

Finally, we call append with the compiled template.

Alternatively, we can use frameworks like Vue, React, or Angular to structure our apps so that we won’t have to worry about mixing HTML and JavaScript is a disorganized way.

The frameworks make us write code in a way that there’s loose coupling between JavaScript and HTML.

Conclusion

We should keep our HTML code out of our JavaScript code.

This way, we won’t have to worry about tracking code that’s hard to debug.

We can separate them with templating systems like Handlebars or use frameworks like Angular, React, or Vue.

Categories
JavaScript Best Practices

Maintainable JavaScript — Null and Object Checks

erral)

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at how to check for various kinds of data.

typeof and null

The typeof operator returns 'object' if the operand is null .

This is one primitive value that we can’t use typeof to check for.

Comparing against null doesn’t give us enough information generally.

However, we can check for null if we use the === and !== operators.

For instance, we can write:

const element = document.querySelector("div");
if (element !== null) {
  element.className = "found";
}

to check if an element with the given selector exists.

querySelector returns null if the element doesn’t exist.

So we can use the !== or === operator to check for the value.

Detecting Reference Values

We can’t use the typeof operator to check for objects.

Therefore, we need another way to check for them.

Objects include object literals and objects created from constructors like Object , Array , Date and Error .

If we use the typeof operator, it always returns 'object' .

This means it won’t be of much use to us.

typeof null also returns 'object' so it’s also not useful for checking for null .

Instead, we can use the instanceof operator to detect values of a particular reference type.

The general syntax of instanceof is:

value instanceof constructor

where value is the value we’re checking for.

And constructor is the constructor that we want to check whether value os created from.

For example, we can use it to detect a Date instance by writing:

if (date instanceof Date) {
  console.log(date.getFullYear());
}

instanceof not only checks the constructor used to create the object.

It also checks for the constructor up the prototype chain.

Since almost all objects inherits from Object , date instanceof Object also returns true .

Therefore, we can’t use value instanceof Object to check for a particular type of object.

The instanceof operator also works with custom types that we defined ourselves.

For instance, we can write:

function Person(name) {
  this.name = name;
}

const jane = new Person("jane");
console.log(jane instanceof Object);
console.log(jane instanceof Person);

Then both console logs will be true .

The first one is true because jane inherits from Object.prototype .

And the 2nd one is true because jane is created from the Person constructor.

The instanceof operator is the only way to detect custom types in JavaScript.

However, it does have one limitation.

If we pass one object from one frame to another.

If we have:

frameAPerson instanceof frameAPerson

then that returns true .

But if we have:

frameAPerson instanceof frameBPerson

then that returns false .

Each frame has its own copy of Person and instanceof only considers the Person in the current frame.

This is the case even if they’re identical.

Conclusion

The instanceof operator lets us check for various kinds of object.

The only ones it can’t check are the ones passed from different frames.

Categories
JavaScript Best Practices

Maintainable JavaScript — Namespaces and Modules

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at namespacing.

Namespaces

It’s possible that we pollute our single global object even if we only created one.

Therefore, if we have one global variable, we organize our code inside it by namespacing the code.

For instance, we can write:

var Books = {};
Books.JavaScript = {};
Books.Ruby = {}

We have the Books global variable and we compartmentalize them with properties.

We have the JavaScript and Ruby properties to organize different things inside the object.

Also, we can add namespaces dynamically by adding properties to our object.

For instance, we can write:

var GlobalObj = {
  namespace(ns) {
    const parts = ns.split(".");
    let object = this;

    for (const part of parts) {
      if (!object[part]) {
        object[part] = {};
      }
      object = object[part];
    }
    return object;
  }
};

We have the namespace function that gets a string that’s separated by dots.

Then we check if the property exists.

If it doesn’t, then we create a property and set it to an empty object.

We do that until all the string parts and added.

And then we return the object .

This way, we can use it by writing:

GlobalObj.namespace('foo.bar.baz');

And we should see the foo property with bar and baz nested in it.

This method gives us freedom to create the namespace and assumes that it exists.

We just run the namespace method beforehand so that we can always use it.

Modules

Since ES6, modules have been part of the JavaScript language.

For instance, we can use them by exporting the members that we want from a module and importing them to use them.

To define a module, we write:

module.js

export const foo = 1;
export const bar = () => {};
export const baz = {};
export default function() {}

to create our JavaScript module with the filename module.js .

Then we can use it by using the import keyword:

import qux, { foo, bar, baz } from "./module";

console.log(qux, foo, bar, baz);

We import the default export as qux .

Default exports are always outside the curly braces and there can only be one of them.

The named exports are in the curly braces and are import with the names that they’re defined as.

We can change the names of default imports to anything.

And we can change the names of named exports with the as keyword.

So we can write:

import qux, { foo, bar, baz as quux } from "./module";

console.log(qux, foo, bar, quux);

The module name can be a relative path or an absolute path.

If it starts with ./ or ../ then it’s a relative path.

./ means relative to the current file.

../ means one directory level up.

Having none of those means it’s an absolute path.

Modules in the node_modules folder can be imported with just the module name.

If something isn’t exported from the module, then they won’t be accessible outside the module.

Conclusion

Namespaces can be added to compartmentalize our code.

Also, we can use modules to export only what we want to expose and keep everything else private.

We can also create smaller files with modules because of that.