Clean Code: JavaScript
This is excellent and you should read it all.
Software engineering principles, from Robert C. Martin’s book Clean Code, adapted for JavaScript. This is not a style guide. It’s a guide to producing readable, reusable, and refactorable software in JavaScript.
Not every principle herein has to be strictly followed, and even fewer will be universally agreed upon. These are guidelines and nothing more, but they are ones codified over many years of collective experience by the authors of Clean Code.
Our craft of software engineering is just a bit over 50 years old, and we are still learning a lot. When software architecture is as old as architecture itself, maybe then we will have harder rules to follow. For now, let these guidelines serve as a touchstone by which to assess the quality of the JavaScript code that you and your team produce.
One more thing: knowing these won’t immediately make you a better software developer, and working with them for many years doesn’t mean you won’t make mistakes. Every piece of code starts as a first draft, like wet clay getting shaped into its final form. Finally, we chisel away the imperfections when we review it with our peers. Don’t beat yourself up for first drafts that need improvement. Beat up the code instead!
Async events in ServiceWorkers with "event.waitUntil"
It’s asynchronous, see? So even though all that network code appears before the
return
statement, it’s pretty much guaranteed to complete after the cache response has been returned. You can verify this by putting in someconsole.log
statements:
Code language: JavaScript
caches.match(request)
.then( responseFromCache => {
if (responseFromCache) {
event.waitUntil(
fetch(request)
.then( responseFromFetch => {
console.log('Got a response from the network.');
caches.open(staticCacheName)
.then( cache => {
cache.put(request, responseFromFetch);
});
})
);
console.log('Got a response from the cache.');
return responseFromCache;
}
Those log statements will appear in this order:
Got a response from the cache. Got a response from the network.
That’s the opposite order in which they appear in the code. Everything inside the
event.waitUntil
part is asynchronous.Here’s the catch: this kind of asynchronous
waitUntil
hasn’t landed in all the browsers yet. The code I’ve written will fail.But never fear! Jake has written a polyfill. All I need to do is include that at the start of my
serviceworker.js
file and I’m good to go:
Code language: JavaScript
// Import Jake's polyfill for async waitUntil
importScripts('/js/async-waituntil.js');
Roving tabindex for keyboard navigation around JavaScript widgets
Setting the
tabindex
of the focused element to “0” ensures that if the user tabs away from the widget and then returns, the selected item within the group retains focus. Note that updating thetabindex
to “0” requires also updating the previously selected item totabindex="-1"
. This technique involves programmatically moving focus in response to key events and updating thetabindex
to reflect the currently focused item. To do this:Bind a key down handler to each element in the group, and when an arrow key is used to move to another element:
- programmatically apply focus to the new element,
- update the
tabindex
of the focused element to “0”, and- update the
tabindex
of the previously focused element to “-1”.Here’s an example of a WAI-ARIA tree view using this technique.
For a more visual explanation, see the following video by Rob Dodson:
The problems with feature detection
The principle of feature detection is really simple. Before you use a particular API you test if it is actually available. If it is not, you can provide an alternative or fail gracefully. Why is this necessary? Well, unlike HTML and CSS, JavaScript can be very unforgiving. If you would use an API without actually testing for its existence and assume it just works you risk that your script will simply throw an error and die when it tries to call the API.
Take the following example:
Code language: JavaScript
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(pos) {
alert('You are at: ' + pos.coords.latitude + ', ' + pos.coords.longitude);
});
}
Before we call the
getCurrentPosition()
function, we actually check if the Geolocation API is available. This is a pattern we see again and again with feature detection.If you look carefully you will notice that we don’t actually test if the
getCurrentPosition()
function is available. We assume it is, becausenavigator.geolocation
exists. But is there actually a guarantee? No.[…]
Cutting the mustard
There is another principle that has gotten very popular lately. By using some very specific feature tests you can make a distinction between old legacy browsers and modern browsers.
Code language: JavaScript
if ('querySelector' in document
&& 'localStorage' in window
&& 'addEventListener' in window)
{
// bootstrap the javascript application
}
In itself it is a perfectly valid way make sure the browser has a certain level of standards support. But at the same time also dangerous, because supporting
querySelector
,localStorage
andaddEventListener
doesn’t say anything about supporting other standards.Even if the browser passes the test, you really still need to do proper feature detection for each and every API you are depending on.
[…]
There are features where the whole premise of feature detection just fails horribly. Some browsers ship features that are so broken that they do not work at all. Sometimes it is a bug, and sometimes it is just pure laziness or incompetence. That may sound harsh, but I’m sure you agree with me at the end of this article.
The most benign variants are simply bugs. Everybody ships bugs. And the good browsers quickly fix them. Take for example Opera 18 which did have the API for Web Notifications, but crashed when you tried to use it. Blink, the rendering engine, actually supported the API, but Opera did not have a proper back-end implementation. And unfortunately this feature got enabled by mistake. I reported it and it was fixed in Opera 19. These things happen.