Organizing JavaScript (or CoffeeScript, Notes from SuperheroJS.com)
Notes from SuperheroJS/Organizing Your Code
Outline
- Designing Better JavaScript API
- Fluent Interfaces: method chaining
- Handling Arguments (Reducing repetition with maps, being flexible with types)
- Async JS
- Callbacks
- Events
- Control Flow Libraries (AsyncJS)
- Promises
Designing Better JavaScript API
"Better APIs developers will love using"
Reference from: Designing Better JavaScript APIs (Smashing Magazine)
Fluent Interface
Method Chaining
-
Heavily used in jQuery
$elem = $("#elemId") $elem.css("background", "orange") $elem.on("click", doSomething) $elem.on("keypress", doAnother) # vs $("#elemId").css("background", "orange") .on("click", doSomething) .on("keypress", doAnother)
-
Another possibility (imaginary SQL-like fluent interface)
query = select(column1, column2) .from(tableName) .where("column", EQUALS, "x") // since this is not a string, u can modify parts out of order query.join(table2) ... // finally get the string query.toString()
- [An example on JSFiddle][fidd le-fluent-sql]
-
Advantages
- Better readability
- Less repetetion/accessing variable
- In some cases, enables more flexibility (like in the fludent SQL API example)
Command Query Separation (CQS)
- Commands changes state (eg. setters)
- Query retrieve values (eg. getters)
- But fluent interfaces returns a self object ...
-
So CQS is broken deliberately
// getter $elem.css("background") // setter $elem.css("background", "red")
Going fluent by extending prototype
// more fluent
Array::map = someFunction
Array::reduce = anotherFunction
// - example usage
arr.map(doSomething)
// less fluent
_.map = someFunction
_.reduce = anotherFunction
// - example usage
_.map(arr, doSomething)
- Subjective
- Extending prototypes can cause unexpected behavior
- Conflicts: what if function is already defined
- What if the implementations (arguments/IO) differ
- Done by some libraries: sugar.js, date.js
- "With great power, comes great responsibility": Use with care
Handling Arguments
Aim to eliminate repetition (eg. use a map)
$elem.css("background", "red")
.css("color", "white")
.css("font-weight", "bold")
// vs
$elem.css({
"background": "red",
"color": "white",
"font-weight": "bold"
})
Being flexible with types
Accept various input types as applicable
myDateObj.until(1370759029735)
myDateObj.until("2013-02-12")
myDateObj.until(new Date(2013, 06, 10))
Named & optional arguments using maps
something = (x1, x2, x3, x4, ...) ->
if x1 is undefined then x1 = someDefault
if x2 is undefined then x2 = someDefault
...
// vs
something = (opts) ->
defaults = {
prop1 = "x",
prop2 = "y",
prop3 = "z"
}
options = $.extend({}, defaults, opts)
Asynchronous JavaScript
- Callbacks
- Events
- Control Flow libraries
- Promises
Callbacks
foo = (doneCallback, errorCallback) ->
// do something
if success
if _.isFunction(doneCallback) then doneCallback(value)
else
if _.isFunction(errorCallback) then errorCallback(err)
Advantages
- Well known pattern
- Very easy to implement
Disadvantages
- Highly nested callbacks (pyramid of doom): hard to read and test
- Only 1 callback per event
Events
Commonly use in GUI applications. Widely used in jQuery and BackboneJS
vent.trigger("someEvent")
// objects can listen to this and handle it
vent.on("someEvent", doSomething)
Advantages
- Well understood pattern
- Many listeners to one object
Disadvantages
- Slightly harder to implement from scratch. But with libraries like jQuery/Backbone, its very easy
Going Asynchronous
Using a control flow library (AsyncJS)
Advantages
- Easier to understand - can visualize flow. top to bottom
Disadvantages
- Functions may need to be adapted for use with AsyncJS (eg. adding done callbacks)
Use deferreds/promises
aLongTask = ->
deferred = $.Deferred()
(doSomething ->
// a long task
if success
deferred.resolve(value)
else
deferred.reject(err)
)()
return deferred
aLongTask().done(handleSucess)
.fail(handleFailure)
- jQuery has Deferred
- Many other libraries providing such functionality
- Useful to know
- AsyncJS: what if u want to wait for multiple async functions to finish. Or even only some?
- Or even ParallelJS, true multi-threading using HTML5 Web Workers
Advantages
- Most powerful
- Can be aggregated, passes around, add listeners when already resolved
Disadvantages
- Least understood?
- Difficult to track with lots of aggregated promises with listeners added along the way?
Written by Jiew Meng
Related protips
3 Responses
+1 for method chaining.
I like it! But it should really be called:
"Organizing CoffeeScript and JavaScript (Notes from SuperheroJS.com)"
because most of the examples (except jQuery) are CoffeeScript code rather than plain JavaScript code.
@josher19, haha, its because this is an outline of a presentation I am doing for work as part of internal training program. And where I work now, we use CoffeeScript