Categories
JavaScript Best Practices

Better JavaScript — Event Queue

Like any kind of apps, JavaScript apps also have to be written well.

Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at ways to improve our JavaScript code.

Don’t Block Event Queue on I/O

JavaScript is a single-threaded language, so blocking I/O with synchronous code is bad if the synchronous code is long-running.

One piece of code can hold up the whole program if we block the event queue.

So we shouldn’t have code like:

const text = downloadSync("http://example.com/file.txt");  
console.log(text);

in our program.

downloadSync waits for the text to be downloaded and then it returns it.

This can take a long time.

Therefore, JavaScript provides us with ways to run code asynchronously.

Instead, we can write:

downloadAsync("http://example.com/file.txt", (text) => {  
  console.log(text);  
});

If we run async JavaScript code, then code is suspended until the result is obtained.

We know the result is obtained when the callback is called.

When the code is suspended, then something queued after this piece of code can run.

We don’t have to worry about some object or variable changing from under us because of concurrently executing code if we structure our code correctly.

Use Callbacks for Asynchronous Code

If we have some simple async code, then we can use callbacks to run our code.

For instance, we had:

downloadAsync("http://example.com/file.txt", (text) => {  
  console.log(text);  
});

that takes a callback that’s called when the result is retrieved from the server.

This is the simplest way to create async code.

When downloadAsync runs, then the function is initiated but hasn’t performed the operation yet.

Once the operation is performed, the callback is run and we get the text .

If we have to run more than one async operation in sequence, then we need to run the 2nd one inside the callback of the first one.

So we can write:

downloadAsync("http://example.com/foo.txt", (text1) => {  
  console.log(text1);  
  downloadAsync("http://example.com/bar.txt", (text2) => {  
    console.log(text2);  
  });  
});

We nest the 2nd downloadAsync call inside the first one.

If there’re more async callbacks we need to run in sequence, then we get more nesting.

This is definitely a problem since we don’t want to nest them.

To avoid this, we can use promises.

Use Promises for Complex Async Operations

If we have lots of async code we need to run in sequence, then the nested async callbacks would be too confusing.

We don’t want to have many levels of nesting in our code since it’s hard to debug and trace.

Instead, we use promises to avoid all the nesting.

For instance, we can create a promise chain by writing:

promise  
  .then((val) => {  
    return promise2;  
  })  
  .then((val) => {  
    return promise3;  
  })  
  .then((val) => {  
    return promise4;  
  })  
  .then((val) => {  
    return promise5;  
  })

We call then with a callback that returns a promise to call another promise.

val has the value that the promise resolved to.

Promises can also be rejected.

To catch errors from rejected promises, we call the catch method:

promise  
  .catch((err) => {  
    console.log(err);  
  })

This is much better than nesting callbacks.

Conclusion

We shouldn’t block I/O of a JavaScript code with long-running synchronous code.

If we have anything that’s potentially long-running, we should use async callbacks or promises.

Categories
JavaScript Best Practices

Better JavaScript — Coercion and Chaining

Like any kind of apps, JavaScript apps also have to be written well.

Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at ways to improve our JavaScript code.

No Excessive Coercion

There should be no excessive data type coercion in our app.

JavaScript is very loose with types, so it coerces data types all the time.

Coercion may be convenient, but it’s easy to produce unexpected results since the rules are hard to remember.

Coercion is confusing when we work with overloaded function signatures.

So if we have:

function foo(x) {
  x = Number(x);
  if (typeof x === "number") {
    //...
  } else {
    //...
  }
};

We converted x to a number before running the rest of the code, so the type of x is always a number.

The else block will never be executed before of the coercion.

If we overload a function by checking the type of the parameter, then we shouldn’t coerce the data type.

Otherwise, we remove the if-else blocks since we don’t need them.

Instead, we can write:

function foo(x) {
  if (typeof x === "number") {
    //...
  } else if (typeof x === "object" && x !== null) {
    //...
  }
};

to check the data type of x for the types before running the code inside the blocks.

We can add type guards with a function.

For instance, we can write:

const guard = {
  guard(x) {
    if (!this.test(x)) {
      throw new TypeError("unexpected type");
    }
  }
};

We check the type with some test method.

When that returns false , we throw an error.

Other objects can use the guard object as its prototype and implement the test method themselves to do the type check.

To create an object with the given prototype, we can call Object.create on it:

const obj = Object.create(guard);

Support Method Chaining

Method chaining makes doing multiple operations in one statement easy.

For instance, with a string, we can call replace multiple times:

str.replace(/&/g, "&")
   .replace(/</g, "&lt;")
   .replace(/>/g, "&gt;")

This is because replace returns a string with the replacements done.

Eliminating temporary variables make it easier to read the code because there’s less distraction.

We can do this with our own methods by returning this .

For instance, we can write:

class Element {
  setBackgroundColor(color) {
    //...
    return this;
  }

  setColor(color) {
    //...
    return this;
  }

  setFontWeight(fontweight) {
    //...
    return this;
  }
}

to create a class with methods that return an instance of itself.

This way, we can chain each method and get the latest result.

For instance, we can use it by writing:

const element = new Element();
element.setBackgroundColor("blue")
  .setColor("green")
  .setFontWeight("bold");

This makes our lives easier since we don’t need any intermediate variables to get to the final result.

Other popular methods that let us change methods include some array methods and jQuery methods.

Conclusion

We shouldn’t coerce data types when we don’t need to.

Also, making methods chainable is also easier for everyone.

Categories
JavaScript Best Practices

Better JavaScript — Async Code

Like any kind of apps, JavaScript apps also have to be written well.

Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at ways to improve our JavaScript code.

Error Handling with Async Code

Like synchronous code, async code errors also need to be handled.

Handling async code errors may be trickier than synchronous code.

Synchronous code errors can be caught with a try-catch block:

try {
  f();
  g();
  h();
} catch (e) {
  //...
}

We catch the errors in the catch block.

And e has whatever is throw in the code in the try block.

Async code comes in a few forms.

If it’s a callback, then some may send the error with the callback.

For instance, Node style callbacks send the error:

fs.readFile('/foo.txt', (err, data) => {
  if (err) {
    throw err;
  }
  console.log(data);
});

In the fs.readFile method, err has the error that’s set when there is one.

We can check for the err value and do something.

Promise errors come in the form of rejected promises.

A promise can be rejected with an error object.

For instance, we can write:

Promise.reject(new Error('error'))

Then we can call the catch method to catch it:

Promise.reject(new Error('error'))
  .catch(err => console.error(err));

The callback’s err parameter has the error.

The async and await looks more like synchronous code.

We can use try-catch to catch rejected promises.

For instance, we can write:

async function foo() {
  try {
    const val1 = await f;
    const val2 = await g;
    const val3 = await h;
  } catch (err) {
    console.error(err);
  }
}

We just wrap our code with try and catch errors with catch like synchronous code.

Async Loops

Async code can be run sequentially in a loop.

The for-await-of loop lets us run async code sequentially.

For instance, we can write:

async function foo() {
  for await (const p of promises) {
    const val = await p;
    console.log(val);
  }
}

We have the for-await-of loop inside the async function which runs the promises in sequence.

It can work with any async iterable object.

Async Callback and Recursion

Async callbacks don’t have any way to run them sequentially easily.

The only way we can do it is with recursion.

For instance, we can write:

function downloadOneAsync(urls, onsuccess, onfailure) {
  const n = urls.length;

  function download(i) {
    if (i >= n) {
      onfailure("error");
      return;
    }
    downloadAsync(urls[i], onsuccess, () => {
      download(i + 1);
    });
  }
  download(0);
}

We have the download function that runs recursively.

The success callback runs the download function once the previous call is successful.

It runs until i reaches n and then stops.

Don’t Block Event Queue on Computations

We shouldn’t block the event queue on complex computations.

If we have some really long-running, then we can create a worker to run it.

Then the task will run in the background.

For instance, we can create a Worker instance by writing:

const myWorker = new Worker('worker.js');

Then we can send messages to it by writing:

myWorker.postMessage(value);

We can listen to messages from the worker by writing:

myWorker.onmessage = function(e) {
  const result = e.data;
  //...
}

Then in worker.js , which is the worker, we can get the message by writing:

onconnect = function(e) {
  const port = e.ports[0];

  port.onmessage = function(e) {
    const data = e.data;
    //...
    port.postMessage(workerResult);
  }
}

The port gets the connection to the main thread.

And e.data gets the data from the main thread sents with myWorker.postMessage .

We can send messages with the postMessage method.

Conclusion

We can loop through promises sequentially.

Also, we can create web workers to run long-running tasks in the background.

The only way to run a series of async callbacks sequentially is with recursion.

Categories
JavaScript Best Practices

Better JavaScript — Arrays, Undefined and Conventions

Like any kind of apps, JavaScript apps also have to be written well.

Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at ways to improve our JavaScript code.

Use Array Methods on Array Like Iterable Objects

Array methods can be used on array-like iterable objects by using the spread operator.

For instance, we can write:

const divs = [...document.querySelector('div')];

Then we return an array of DOM elements.

querySelector with a NodeList of DOM elements.

Then we used the spread operator to turn the array of elements into an array.

We can do the same thing with strings and other iterable objects.

If we have a non-iterable array object, then we can convert it to an array with the Array.from method.

For instance, we can write:

const arrayLike = {
  0: "a",
  1: "b",
  2: "c",
  length: 3
};

const arr = Array.from(arrayLike);

We have numeric indexes and the length property in the object so we can use Array.from to convert it to an array.

Prefer Array Literals to the Array Constructors

Array literals are shorter than using the Array constructor to create arrays.

So we should use the array literals to create our arrays.

We can write:

const a = [1, 2, 3, 4, 5];

instead of:

const a = new Array(1, 2, 3, 4, 5);

There’re problems with the Array constructor.

If we have to make sure Array hasn’t been modified.

And if it has one argument, it works differently than if it has multiple arguments.

If it has one argument, then it creates an empty array with the given number of slots if the argument is a number.

If it has multiple arguments, then it creates an array with the arguments.

Maintain Consistent Conventions

When we create libraries, we should create interfaces with consistent conventions so we don’t have to look up the names all the time.

For instance, we create constructors with Pascal case names.

And we create functions, variables, and properties with camelCase names.

For function, if we have arguments, then they should have consistent types and order.

This way, we can predict the order and type and work faster.

Sticking to conventions reduces confusion and makes working with them more predictable since we don’t have to look up stuff all the time.

Treat undefined as No Value

undefined is a speicla value in JavaScript.

It means no specific value.

Unassigned values has value undefined .

So if we have:

let x;

then x is undefined .

If we have a return statement without a value, then it returns undefined .

So if we have:

function f() {
  return;
}

Then f() returns undefined .

This is the same as:

function g() { }

And g() also returns undefined .

Function parameters that have bo argument passed into it is undefined .

For instance, if we have:

function f(x) {
  return x;
}

Then calling f without an argument would make x undefined .

Treating undefined as the absence of a value is convention established by the language.

Conclusion

Array-like objects can be converted to an array with the spread operator.

Also, APIs should have consistent interfaces and conventions.

And undefined should be treated as no value.

Categories
JavaScript Best Practices

Better JavaScript — Arrays and Properties

Like any kind of apps, JavaScript apps also have to be written well.

Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at ways to improve our JavaScript code.

Calling hasOwnProperty Safely

The hasOwnProperty is part of Object.prototype , which means that it can be overridden or removed.

So to make sure that it’s always available, we can call it directly from the Object.prototype .

We can write:

const hasOwnProperty = Object.prototype.hasOwnProperty;

or:

const hasOwnProperty = {}.hasOwnProperty;

Then we can call it by writing:

hasOwnProperty.call(dict, 'james');

given that we have:

const dict = {
  james: 33,
  bob: 22,
  mary: 41
};

We can abstract the logic for checking entries our of the object.

For instance, we can create a class with the items.

We can write:

class Dict {
  constructor(elements) {
    this.elements = elements;
  }

  has(key) {
    return {}.hasOwnProperty.call(this.elements, key);
  }

  set(key, val) {
    this.elements[key] = val;
  }

  get(key) {
    return this.elements[key];
  }

  remove(key) {
    delete this.elements[key];
  }
}

We created a Dict class to hold an elements instance variable that we can manipulate with methods for doing common dictionary operations.

has checks if the property exists.

set sets the key with the value.

get gets the value with the given key.

remove removes an entry from the object.

Now we can use it by writing:

const dict = new Dict({
  james: 33,
  bob: 22,
  mary: 41
});

Use Arrays for Ordered Collections

Arrays should be used for ordered collections.

Ordered collections have an index and they are iterated through with the given order.

We can create an array by writing:

const arr = [1, 2, 3];

Then we can use it by writing:

for (const a of arr) {
  console.log(a);
}

We looped through the arr array with the for-of loop.

It always iterates from the start to the end, so the order is predictable.

The for-of loop shouldn’t be mistaken for the for-in loop which loops through items in an unpredictable order.

Never Add Enumerable Properties to Object.prototype

We shouldn’t add enumerable properties to Object.prototype .

Object.prototype is a property that we don’t own so we shouldn’t change it since it’ll give results that most people don’t expect.

Also, the for-in loop will pick up the enumerable property of a prototype so it’ll be iterated through by it.

We don’t want that to happen.

If we want to add a property to Object.prototype , then it should be made non-enumerable.

For instance, we can write:

Object.defineProperty(Object.prototype, "allKeys", {
  value() {
    const result = [];
    for (const key in this) {
      result.push(key);
    }
    return result;
  },
  writable: true,
  enumerable: false,
  configurable: true
});

We set enumerable to false so that it won’t be picked up by the for-in loop.

Conclusion

We shouldn’t add enumerable properties to Object.prototype .

Also, to call hasOwnProperty safely, we shouldn’t call it directly from the object itself since it can be modified.

Arrays are good for ordered collections.