Advanced Javascript Tips and Optimization

25 Apr 2018

I’ve been working with Javascript for a few years now. It’s a wonderfully strange language, and there’s a lot of ways you can start a project and write code.

Here is what I think I know, as of April 2018. This guidance is heavily biased by working with Electron/React/Node, all over the stack, over multiple types of projects.

You need types

Developing a modern JS application without typing is both painful and tedious. I helped write a 25k+ LOC Node codebase without any form of typing - let me tell you, when you are debugging a year-old function you wrote and try to piece together what the data structure must have been at the time - it’s nearly impossible.

Any codebase that goes beyond a toy/prototype level must immediately implement TypeScript, Flow, or some form of typing, even if it is just a ridiculously long comment above each function describing the expected inputs/outputs (don’t actually do this - oh wait, this basically describes working with Python).

Make business-logic strings constant variables

If you frequently find yourself writing code like this:

if (Animal.type === 'dog') {
    // woof
}

Let me give you some advice. One day, one of your amazing engineers is going to write dogg instead of dog, and you’re going to miss it because you’re too busy making coffee, and that dumb code is going to end up in production, and your users are going to be rudely greeted by a stupid cat (or an error) instead of an awesome dog when they click “SHOW ME DOGS” on your new app, DogCatHorseShow!.

Instead, try this. The first time you write a comparison like above, stop yourself, take a deep breath, and do this.

Create a file called constants.ts (you’re using TypeScript, right? :))

The file should look like this:

export const kDogType = 'dog'
export const kCatType = 'cat'
export const kHorseType = 'majestic filly'

And now, your business logic file should look like this:

import { kDogType } from './constants';
if (Animal.type === kDogType) {
    // woof
}

Now you and your co-workers can safely compare values and you know that you’re all working with the same strings. If this example seems contrived, I want you to know that I just spent a lot of time fixing errors related to followup vs follow-up.

Consolidate your business logic EARLY, or you’ll end up with important comparisons scattered everywhere and subtle errors permeating your code.

Don’t mix data types in arrays

Although the Chrome team is making admirable gains in performance here, it is ALWAYS slower to include mixed data types in an array.

// This is bad and slow
const slow = ['string', null, {}, 'this is bad', [ { a: 'b' } ], 10]

// This is fast and good
const fast = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

You can see this performance impact for yourself here: JSPerf

Don’t write “multi-purpose” functions

The V8 engine can’t optimize functions that are passed varying data types. If you really are trying to eke out every bit of performance, write pure functions that accept consistent data types, and make sure you only use them as intended!

Operations are monomorphic if the hidden classes of inputs are always the same - otherwise they are polymorphic, meaning some of the arguments can change type across different calls to the operation. - from Performance Tips for JavaScript in V8.

// This function seems innocent enough, right?
const add = (a, b) => a + b

add(1, 2)     // Returns 3. So far, so good
add('1', '2') // Returns '12'. This is getting bad
add({}, [])   // V8 throws its hands in the air and says "screw this"

Abusing functions like this, while convenient for the programmer, can lead to a nearly 50% reduction in speed.

Yikes!

Bundle operations that involve external resources

This one is probably a no-brainer, but consider batching any events or actions that rely on external resources to complete.

When you’re updating a JSON file or a database somewhere, you’re going to deal with incredible asynchronous headaches if your code has a theme of “each individual action manages itself.”

Consider writing your code in a way that lends itself to batching of updates - whether you’re doing HTTP requests, file updates, DOM reflows, or DB calls. Focusing on batching early in your codebase will allow you to scale faster when you start handling more actions and events.

Keep functions small

You should already do this just for cleanliness and understandability, but you should also know that the length of your written function (including comments(!!)) has an impact on performance.

The takeaway here should still be to keep functions small. At the moment we still have to avoid over-commenting (and even whitespace) inside functions. - from v8-perf discussion on Github

Write tests early and often

In a greenfield codebase, every feature added to the codebase should have an accompanying test. This prevents code rot from setting in, and it keeps the codebase style reasonable and testable from an early stage.

Especially when you’re doing open-source driven work, you’re frequently going to have subtle breakages when you upgrade/update your npm packages. Automated tests are the only way you’re going to catch it - yes, even for your weekend project that you’ll definitely never pick up again.

Look, I’m not saying you gotta go all TDD on your personal projects, but… one day you’ll want to go back to that project and use it, but you won’t be able to touch the code without breaking it. Been there, done that, learned my lesson.


Sea of Thieves - Couldn't install. We'll retry shortly

28 Mar 2018

If you’re getting the error "Couldn't install. We'll retry shortly" (error code 0x80070006) when trying to install Sea of Thieves through the Microsoft store, try this.

  1. Navigate to C:\Users\[your_user_name]\AppData\Local\Packages\Microsoft.WindowsStore_8wekyb3d8bbwe\LocalCache
  2. Delete all the folders & files in LocalCache - leave the LocalCache folder.
  3. Reboot and retry

If that doesn’t work, try the following:

  1. Open Windows Powershell as Admin
  2. Paste set-ExecutionPolicy RemoteSigned into the terminal and hit Enter.
  3. Press Y to accept.
  4. Paste Get-AppXPackage -AllUsers | Foreach {Add-AppxPackage -DisableDevelopmentMode -Register "$($_.InstallLocation)\AppxManifest.xml"} (all one line) and hit Enter
  5. A lot of stuff will happen. Once it’s done, exit the Powershell terminal.
  6. Reboot and retry

Still not working?

  1. Open a CMD prompt as Admin
  2. Try the following commands one by one (paste + Enter)
    • dism /online /cleanup-image /startcomponentcleanup
    • sfc /scannow
  3. Reboot and retry

I haven’t tested this, but you can try creating a new user and change the new user account from standard to administrator, then login with the new user and test opening and updating store.

For Error 0x80070005 please refer here

AWS Elastic Load Balancer - Force Redirect to HTTPS

09 Feb 2018

In response to the Chrome team’s news that Chrome 68 will mark all non-HTTPS sites as “Non-Secure”, I’ve decided to get this site set up with SSL. As always, my encounters with tech were mildly frustrating, and I’ve documented my solution to an issue below.

Some reference for how this site is set up - I build my site locally, upload it to an S3 bucket. An EC2 instance copies any changes made to the S3 bucket every minute and serves the S3 content. The EC2 instance is tucked behind an Elastic Load Balancer, and it’s all managed by Route 53.

So, you have your ELB set up pointing to your EC2 instance and want to force your user into using HTTPS.

Add the following lines to your /etc/httpd/conf/http.conf (Apache only).

<Directory>
    # You may have existing parameters here. Don't touch.
    RewriteEngine on 
    RewriteCond %{HTTP:X-Forwarded-Proto} ^http$
    RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
    # You may have existing parameters here. Don't touch.
</Directory>

This would redirect any HTTP traffic coming to our root directory to HTTPS. Enjoy!


Dynamically Resizing Kibana iFrames with Javascript and HTML

19 Jan 2018

If you’re struggling to dynamically resize your Kibana iFrames, you will need two things: Javascript and HTML.

Below is some inline Javascript that will be run onload for each iframe. It is important to note that this script will only work on Kibana tables - not other types of visualizations.

Basically, the script checks to see if the Kibana table has loaded yet - if not, it resets the function with a recursive loop.

Once the Kibana iframe’s table has loaded, we grab that height, and resize the iframe accordingly.

<script>
/* Waits 1 second then checks the iframe again */
function wait_for_load(id) {
  return setTimeout(() => setIframeHeight(id), 1000)
}

/* 
   Called by each iframe onload. 
   Checks if the Kibana table has loaded
   And resizes the iframe when it does
*/
function setIframeHeight(id) {
  if (!id) return;
  const ifrm = document.getElementById(id); // Get our iFrame element using the id
  if (!ifrm) return;

  // If the Kibana iframe table hasn't fully loaded yet, restart this function after a wait
  if (!ifrm.contentDocument || !ifrm.contentDocument.body || 
      ifrm.contentDocument.body.getElementsByTagName('table').length === 0) {
    return wait_for_load(id)
  }

  // Finally! The table has loaded. Let's get the table height and resize the iframe
  const table_height = ifrm.contentDocument.body.getElementsByTagName('table')[0].offsetHeight + 46;
  // The extra 46px of padding helps us avoid IE and Chrome scrollbars on Windows
  ifrm.style.height = table_height + 'px';  
  console.log('We exited the loop for ' + id + ' after setting a height of ' + table_height + 'px');
}
</script>

<!-- Note that the iframes have their onload attribute set to call the above functions -->
<div>
    <iframe id="table1" style="width: 100%;" 
            src="http://your-kibana-source1" 
            frameborder="0" scrolling="no" 
            onload="setIframeHeight(this.id)">
    </iframe>
    <iframe id="table2" style="width: 100%;" 
            src="your-kibana-source2" 
            frameborder="0" scrolling="no" 
            onload="setIframeHeight(this.id)">
    </iframe>
</div>