blog

Image of hurdles by Josh Boak on Unsplash

Hurdles: Jump Small Problems With Some Quick Code Snippets

by

During the course of any complex project (and even many simple ones), on the way to accomplish the actual goal, you’re certain to encounter any number of small hurdles along the way – little problems which need to be resolved for the bigger picture to come into focus.

Below, in no particular order, are some of the ‘little’ problems I’ve encountered, and solutions thereto – hopefully giving you a leg up so that when and if you encounter something similar, you can jump it without breaking stride.

Is that a Promise?

To determine whether a Javascript object implements the promise interface, we’ll have to rely on duck typing. This is generally safe (outside of a potentially hostile environment), and if you’re suspicious that tested objects might return false positives, you can add/change what is tested tailored to the specific implementation you’re using.

For instance, you might add a test on functions like ‘promise’ and ‘pipe’ being present for jQuery Deferred. This could also be stretched to try and tell one implementation apart from another, if you’re mixing them for whatever reason.

    /**
     * Determine whether object implements promises.
     * @param object
     * @returns {*}
     */
    isPromise:function(object){
        return (typeof object.then === 'function');
    }

IE Console woes, log only in debug mode

It’s extremely common to litter your js with some console.log calls while developing, in order to get a better handle on inputs, outpus, variable states, etc. Some of these calls are obviated by the incresingly powerful built-in browser tools, but inevitably your code will pick up a few.

Suddenly, you realize that you need to port that code to an IE version <= 8. The code breaks. You open up devtools. The code works. What’s going on? The console object doesn’t exist until you open up devtools – its tricky the first time you stumble into it. Here’s the quick fix:

window.console = window.console || {
    /** @param {...*} args */
    log:function(){};
};

Have code that needs to be tested on a production server, but want to leave those console.log calls in for future debugging use – but not have them output when you’re on the production server? Set a variable (either global if you must, or better, in an init call to a function, on the function prototype, etc.) to indicate ‘debug mode’ and call console.log like so:

this.debug && console.log("Console output.");

Getting a trim, Capitals and splitting camelCase

It seems that even libraries purporting to support older browsers expect the String.prototype.trim function to be present (see the Google Maps v3 api, for instance). Unfortunately, IE versions <= 8 and Safari <= 5 don’t. We’ll just quickly pop that in to place, then:

if (typeof String.prototype.trim !== 'function'){
    String.prototype.trim = function(){
        return this.replace(/^s+|s+$/g, '');
    };
}

If you have input strings that you know will need capitalizing (say database column names that will end up in the th of an html table), best to wrap that in an easily callable, chainable function:

String.prototype.capitalize = function(){
    return this.charAt(0).toUpperCase() + this.slice(1);
};

We can do something similar if we know we’ll need to split a string on camelCaseNames:

String.prototype.splitCamelCase = function(){
    return this.replace(/([A-Z])/g, ' $1');
};

If you cringe at the idea of adding to the built-in prototypes, we can do something similar with, for example underscore’s mixins:

_.mixin({
    capitalize:function(string){
        return string.charAt(0).toUpperCase() + string.slice(1);
    },
    splitCamelCase:function(string){
        return string.replace(/([A-Z])/g, ' $1');
    }
});

This Space Reserved

So, you’ve created an object somewhere in your code, probably something with meaningful names given the function their value is expected to be used for/perform, and run it, only to be met with an error: ‘Expected identifier, string or number’. Chances are you’ve accidentally used a reserved keyword in your object keys.

Something like this:

var ob = {
    for:12,
    class:15
};

Needs to be altered to this:

var ob = {
    'for':12,
    'class':15
};

This will keep the js engine in question from seeing a keyword where an object key was intended. Another alternative is, of course, to simply avoid using any of the reserved keywords as object keys.

Expert Timing

setTimeout/setInterval is not very accurate. It has to compete with ui events and other callbacks, and really only guarantees that at least the specified interval will have passed before it gets called. Usually, its not an issue. If it is important that the timing be accurate, however, we’ll have to work a little harder.

In modern browsers, we can rely on window.performance.now() for a highly accurate (10 us) timestamp. In older browsers, we’ll have to make do with ms resolution from Date(). Either way, we’ll be altering our desired interval with this number in our next call for the setTimeout, in order to try and keep the callbacks happening as regularly as possible, accounting for delays. This is still subject to variation, and very short intervals likely won’t be long enough to allow for us to account for said variation, but the result should still be more accurate than a simple setTimeout/setInterval call, in a way that offers a similar api to the standard setInterval call.

(function(window){
    window.performance = window.performance || {};
    performance.now = performance.now || function(){
        return +new Date();
    };
    function AccurateInterval(options){
        this.startTime = 0;
        this.elapsed = 0;
        this.timeout = 0;
        this.interval = options.interval || 100;
        this.callback = options.callback;
        if (typeof this.callback !== 'function') throw 'You must specify a callback function.';
        return this;
    }
    AccurateInterval.prototype.start = function(){
        this.startTime = performance.now();
        this.timeout = window.setTimeout(this.tick.bind(this), this.interval);
        return this;
    };
    AccurateInterval.prototype.tick = function(){
        this.elapsed += this.interval;
        var missedTicks = 0,
            nextInterval = this.interval - ((performance.now() - this.startTime) - this.elapsed);
        if (nextInterval <= 0) {
            missedTicks = (Math.abs(nextInterval) / this.interval) | 0;
            this.elapsed += missedTicks * this.interval;
            this.tick();
            return;
        }
        this.callback();
        this.timeout = window.setTimeout(this.tick.bind(this), nextInterval);
    };
    AccurateInterval.prototype.stop = function(){
        window.clearTimeout(this.timeout);
        return this;
    };
    window.setAccurateInterval = function(callback, interval){
        return new AccurateInterval({callback:callback, interval:interval}).start();
    };
    window.clearAccurateInterval = function(acc){
        acc.stop();
    };
})(window);

Hopefully, one or more of the above offers you a quick solution so you can get back to the real problem you were supposed to be solving… and if not, at least offer you some solace as you struggle – we’ve all hit a few little hurdles along the way.

+ more