Do you write most of your javaScript by making sure it runs first in the browser, adding debugger and console.log statements along the way. If so, you may be hindering the speed at which you write javaScript and the quality of your code.
Tunnel code
When you focus first on only whether or not your code runs without errors, the vision of your code is narrow.
It can be easy to look at writing code as just a series of solving problems where each solution is a patchwork of unrelated pieces farmed from jumping from one stackoverflow search to the next.
You may be tempted to believe this is the fastest way to write javaScript.
How much time do you lose tracking down bugs you do not entirely understand, just to get the thing working.
How often do you get frustrated because your code is broken and if you could just figure out why then you could move on to the next problem and also why is the browser taking so long to load.
Greater vision
I challenge you to set your gaze higher. Forget about whether or not your code actually works in the browser, that can come later. In fact, forget about the browser altogether at first.
Step back and forget about the code at first too. Think about what you are trying to build, the whole system.
What are the pieces that will make up the whole system? How do they relate? What would be the best way to organize them? Write it down.
Now start writing your code.
Small, incremental tests
As you start writing your code, write tests to make sure it works. While you will write more code then you would if you had just started writing code using the browser, this approach will actually save you more time.
The reason is that there are a lot of small, inconvenient things that even a genius could be forgiven for missing.
That value you require from a function that needs to be a float, well turns out it returns a string.
The closure that is supposed to return a new value, well turns out it just returns the same value over and over.
These problems are much easier to discover and fix with tests then in the browser.
I recommend many, short tests over a few, large tests. Nothing is too dumb to test. It is often the obvious, simple things which have bugs.
How
The test-driven development process has three steps;
Write a test that fails.
Write enough code to make the test pass.
Refactor the code to tidy it up, ensure it is readable and maintainable. Run all the tests to ensure there are no regressions.
The first step is important because you can accidentally write broken tests that always pass. In the second step, you focus primarily on solving the test. In the third step you focus on the quality of your code.
Relax
I believe that writing code should be fun or at least stress free. Using a test-driven development approach has a number of benefits that can improve your javaScript workflow and make writing it more enjoyable;
Deeper understanding of your code. Writing tests will force you to think more carefully about what you are trying to achieve. As you become more clear about your goals and the architecture of your code, it will be easier to write code.
Slow and steady wins the race. It sets the goal on the simple task of writing code in small chunks. You are more focused on the process rather then the end result and are more likely to write code that is deliberate and well-considered. You will gradually gain confidence in your code and the process and make fewer mistakes.
Faster debugging. It will be easier to narrow the scope of bugs that show up in your code. You can be sure that you have fixed certain bugs and can look elsewhere. You can re-run your tests and have more confidence that changes you make to your code have not broken anything.
I setup webpack at my company so I am often asked to debug issues other devs have with npm packages. My debug strategy is pretty straightforward;
Ask for a stacktrace of the error
Follow it from the bottom to the top
As an example, lets look at this stacktrace I was given.
This is a problem compiling javaScript with webpack in docker. Looking at the line second to the bottom I can see that it is complaining about a missing file.
Now looking at the first line in the stacktrace, I can see that the npm module language-tags can’t find the file index in language-subtag-registry.
Looking for dependency
My first thought is that language-subtag-registry is a dependency in language-tags. I visit the npm page for language-tags and sure enough, it lists language-subtag-registry as a dependency.
boom
I ask the dev to install language-subtag-registry via npm.
No luck. The dev gets the same error after installing the package via npm.
Reinstall node-modules
Ok, my next thought is that maybe the devs node-modules folder is out of sync for some reason and a good `ol cache clean + reinstall can solve this. I ask the dev to run this command;
No luck. Dev gets the same error again.
Dig into npm package code on github
At this point I need to really dig into the code and see whats going on here.
The very first line in the stacktrace (ERROR in ./~/language-tags/lib/index.js) tells me where the problem occurs so I visit the github page for language-tags and look for the index.js in the language-tags/lib directory.
I see that the file is importing language-subtag-registry/data/json/index, which exactly matches the error on the second line of the stacktrace, Module not found: Error: Cannot resolve module 'language-subtag-registry/data/json/index' in /heron-docker/node_modules/language-tags/lib. There is a problem importing index.
I realize index is a JSON file and webpack needs to look for the .json extension, otherwise, it will think it is a .js file. I ask the dev to include .json in the webpack extensions settings.
Problem; how to mount a React component that requires data from a webservice on many other parts of a page, many times (that also each make their own network requests) without duplicating requests.
Polling to the rescue
This bit of code is embedded inside the ajax promises of other parts of the page. They fetch their own data then need this React component to fetch it’s own data and display it.
First it checks if the React component has data in it’s Redux store. If so, it uses that data to render itself.
If it finds that it has no data in storage but is in the process of requesting it, it will poll for the request to finish by looking at the ‘isFetching’ state in the Redux store, then use that stored data when the ‘isFetching’ state finishes.
Finally, if there is no data in storage and no request is happening, it will make a new request.
-
This provided a clean solution to a messy situation where I needed my React code to be “dummy proof”. That is, any other engineer could just embed it in any other parts of the site (often multiple times and at multiple points, inside the promises of other network requests) without knowing a thing about it and it would just work without making duplicate requests.
I have been using axios to make network requests to a webservice from a React + Redux app. For testing the requests from the React app, I have been using axios-mock-adapter. The following is the action which makes the network request that I want to test.
It uses a Redux thunk to dispatch the request action retrieveCollectionRequest() before the network request happens.
The next action, retrieveSingleCollectionSuccess(response)) happens after the request is successfully resolved. This action just passes the response from the request to my reducer which stores the response in my Redux store.
I dispatch the action elsewhere in one of my components.
I want to test that when I dispatch this action it makes a network request to my webservice then stores the response in my Redux store if it is successful.
The MockAdapter replys to my GET request with a 200 status code so that the request resolves successfully and passes in a collection as the response. collectionsState is the object in my Redux store where I store the response. I test that the collection which shows up in the collectionsState matches the data in the mock.onGet() reply handler.
Debugging
I had a few issues with this test at first. The data that I used in the reply handler to respond to my request did not match the data that the request resolved. This resulted in my request receiving a 404 status code.
I also wanted to see what my request was doing when the mock adapter handles it. To accomplish this I can use this bit of code to inspect it;
The github repo for axios-mock-adapter has some nice tests itself which helped me resolve the bugs I ran into.
ES6 has some new tools for dealing with objects and arrays. These include destructuring assignment, enhanced object literals and the spread operator.
Destructuring Assignment
Object destructuring
Object destructuring allows us to pull in specific attributes from an object and scope it locally. Consider the following cat object (also known as an object literal).
We can pull out attributes and create local variables out of them. These variables can be changed without changing the original cat object.
Just as object literals allow us to create multiple properties at once, we can destructure them, by extracting multiple properties at once. This is considered an object pattern.
This is the same thing as doing the following in ES5.
We can also destructure objects that are arguments in functions. Consider the following function that logs the cats gender.
This is the same thing as doing the following;
Instead of using dot notation to access the color of the cat object literal within the function, we can destructure the cat object and pull out it’s color attribute.
This is a more declarative approach because we signal that we are only using the color attribute from the cat object.
Array destructuring
In ES6 we can pull out values from arrays and scope them to local variables.
You can pass over values in arrays using commas to get to specific indexes.
You can get all the remaining elements of an array using the rest operator. This operator looks like the spread operator in ES6 (…) except it is used inside function calls and arrays.
You can use array destructuring to swap values in variables.
This is the same as the following in ES5.
Enhanced Object Literals
Object literal enhancement allows us to create objects from variables that are in scope.
color and age are now attributes in the new cat object. Object literal enhancement can also include functions.
We can write a shorthand version of this without the function operator.
We can write shorthand for objects whose properties are initialised by variables of the same name.
This is the same as writing the following in ES5.
This could be quite handy in functions or modules that export objects.
This is the same as writing the following in ES5.
Spread Operator
The spread operator is three dots (…) and allows us to do powerful new things with arrays.
Copy
We can copy arrays.
The array copy animalsCopy is separate from the animals array and is unaffected by changes to animalsCopy.
This is a great way to deal with immutable data that uses arrays.
Function arguments
The spread operator can be used in functions to collect arguments as an array. Consider this use of appply.
This can be called in ES6 using the spread operator like this;
Inversely, we can create a function that accepts n number of arguments as an array then uses the spread operator to operate on them.
-
I highly recommend using the Babel Repl to play around with ES6 code.