Here are some common time sinks encountered when building a JavaScript app, along with some tips to avoid them.
Note: these tips were originally shared as a part of my TulsaWebDevs presentation following 2012 Startup Weekend Tulsa.
Bind π
In JavaScript, scope is resolved during a function’s execution – not its definition. When working with classes, you expect that this
will point to the class, but it often won’t.
Example:
var Todo = Backbone.View.extend({
events: {
'click input': 'check'
},
check: function() {
console.log(this);
}
});
Solution 1:
Use _.bindAll()
var Todo = Backbone.View.extend({
events: {
'click input': 'check'
},
initialize: function() {
_.bindAll(this, 'check');
},
check: function() {
console.log(this);
}
});
Solution 2:
Use CoffeeScript =>
class Todo extends Backbone.View
events:
'click input': 'check'
check: =>
console.log this
Callback Spaghetti π
As your app grows more complicated, your code will start to look like this (unless you work hard to avoid it):
before(function (done) {
server.server(options, function (s, db, providers) {
//clear db and add a test user - "testuser"
db.user.remove({}, function () {
db.notification.remove({}, function () {
providers.provider1.insertBulk(item1, item2, item3],
function (err, result) {
providers.provider2.insert([item1, item2, item3]
function (err, result) {
providers.provider3.insert([item1, item2, item3]
function (err, result) {
providers.provider4.insert([item1, item2, item3],
function (err, result) {
s.listen();
done();
})
});
});
});
});
});
});
});
While I have no perfect solution, here are some tips:
1. Split callbacks out into separate methods on the class:
click: function() {
$.get('/foo', function(data) {
// do something
});
}
…becomes:
click: function() {
$.get('/foo', this.clickCallback);
}
clickCallback: function(data) {
// do something
}
Rinse, repeat.
2. Use the async or Seq library
Waterfall:
async.waterfall([
function(callback){
callback(null, 'one', 'two');
},
function(arg1, arg2, callback){
callback(null, 'three');
},
function(arg1, callback){
callback(null, 'done');
}
], function (err, result) {
// done
});
forEach:
async.forEach(files, this.saveFile, this.complete);
Supervisor Pegging CPU π
Supervisor monitors files for changes; if you have many files, your CPU starts to become pegged.
Solution: Ignore the node modules directory and any other directories not containing source code:
supervisor -i data,node_modules app.js
Supervisor doesn’t reload configs π
Solution: um, be aware of this fact, and just ctrl-c and start supervisor again when you change a config. :-)
Object is not a function π
This error is the bane of my existence. It happens in lots of places, for many different reasons, but here are a few that I always try to check first:
- module.exports is not set
- when using cs, forgetting a comma, e.g.
fn bar 2
instead offn bar, 2
- setting a property on your object with the same name as a method
Backbone - visibility into view(s) π
If you’re building a Backbone.js app, do yourself a favor, and set the main app view as window.app_view
or something similar. Set other views as subviews on the main view.
This will allow you to inspect the app from FireBug after everything is up and running.
Sometimes console.log() lies - object changes after being logged π
In FireBug, doing a console.log
on an object can be misleading if the object changes soon after it is logged. FireBug will update the nested properties of the object.
Solution: to be sure, you should console.log(obj.foo.bar)
to see the actual value of the property at the time it is logged.