Created
March 31, 2012 17:41
-
-
Save kixxauth/2267033 to your computer and use it in GitHub Desktop.
JavaScript Objects and Promises
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Typical Prototype composure in JS; This is the classical way of creating and | |
// using objects in JS. | |
// | |
// "Super" or "Parent" object prototype | |
function Cat(color) { | |
this.color = color; | |
this.saying = 'meowwww'; | |
} | |
Cat.prototype = { | |
speak: function () { | |
return this.saying; | |
}, | |
hasTail: function () { | |
return true; | |
} | |
}; | |
// "Sub" or "Child" object prototype | |
function Kitten() { | |
this.color = color; | |
} | |
Kitten.prototype = new Cat(); | |
Kitten.prototype.speak = function () { | |
return 'mewww'; | |
}; | |
var fifi = new Kitten('grey'); | |
fifi.speak(); | |
// | |
// A "somewhat new" and "maybe better" way of Object construction in JS: | |
// | |
// We need a special function to extend super types. | |
function extend(m) { | |
var m; | |
for (m in parent) { | |
if (!this.hasOwnProperty(m)) { | |
this[m] = parent[m]; | |
} | |
} | |
} | |
// The "trick" is the new `Object.create()` method found in ECMAScript 5 | |
// comlient JS environments. | |
var Cat = { | |
speak: function () { | |
return this.saying; | |
}, | |
hasTail: function () { | |
return this.tail; | |
}, | |
create: function (color) { | |
var self = Object.create(Cat); | |
self.color = color; | |
self.saying = 'meowwww'; | |
self.tail = true; | |
return self; | |
} | |
}; | |
var Kitten = { | |
speak: function () { | |
return 'mewww'; | |
}, | |
create: function (color) { | |
var self = Object.create(Kitten); | |
self.color = color; | |
self.tail = true; | |
return self; | |
}, | |
extend: extend | |
}; | |
Kitten.extend(Cat); | |
var fifi = Kitten.create('grey'); | |
fifi.speak(); | |
// | |
// Taking better advantage of Prototypal Composure and adding private members | |
// using "create" or "maker" functions. | |
// AKA: The Module Pattern | |
// | |
function speak() { | |
return this.saying; | |
} | |
function hasTail() { | |
return this.tail; | |
} | |
function createCat(color) { | |
var self = {color: color, tail: true, saying: 'meowww'}; | |
self.hasTail = hasTail; | |
self.speak = speak; | |
return self; | |
} | |
function createKitten(color, secret) { | |
var self = {color: color, tail: true}; | |
self.hasTail = hasTail; | |
self.speak = function () { | |
return 'mewww'; | |
}; | |
self.revealSecret = function () { | |
return secret; | |
}; | |
return self; | |
} | |
var fifi = createKitten('grey', "I'm really a baby tiger."); | |
fifi.speak(); | |
fifi.revealSecret(); | |
// Problem? The user can change the object on the fly, and we might not want | |
// then to do this. | |
fifi.tail = false; | |
fifi.hasTail(); | |
// However, when we want to have a secret, we can expose it privately with | |
// the "maker" function. | |
fifi.secret = "An evil secret"; // User tries to change the secret. | |
fifi.revealSecret() // Will return "I'm really a baby tiger." | |
// Takeaway: | |
// The user cannot change the secret, in any way. | |
// Problem? What happens when we call these methods? What will `this` be? | |
speak(); | |
hasTail(); | |
// Takeaway: | |
// You should never call these globally defined methods unless they have been | |
// attached to an object, otherwise `this` will be set as the global `window` | |
// object. | |
// | |
// The Callback Spaghetti or "Pyramid of Doom" problem. | |
// | |
$('button').click(function () { | |
getTwitterUsers(function (err, users) { | |
if (err) { | |
return alert(err); | |
} | |
var twitterUsers = ''; | |
function getMore() { | |
if (!users.length) { | |
$('ul').html(twitterUsers); | |
return; | |
} | |
var id = users.shift(); | |
getTwitterUser(id, function (err, userData) { | |
if (err) { | |
return alert(err); | |
} | |
twitterUsers += ('<li>'+ userData +'</li>'); | |
getMore(); | |
}); | |
}; | |
getMore(); | |
}); | |
}); | |
// | |
// Refactor the feature and compose it with reusable functions. | |
// | |
function composeUserHTML(users, callback) { | |
var twitterUsersHTML = ''; | |
function getMore() { | |
if (!users.length) { | |
return callback(twitterUsersHTML); | |
} | |
var id = users.shift(); | |
getTwitterUser(id, function (err, userData) { | |
if (err) { | |
return alert(err); | |
} | |
twitterUsersHTML += ('<li>'+ userData +'</li>'); | |
getMore(); | |
}); | |
} | |
getMore(); | |
} | |
function updateUsersDOM(html) { | |
$('ul').html(twitterUsers); | |
} | |
function updateTwitterUsers() { | |
getTwitterUsers(function (err, users) { | |
if (err) { | |
return alert(err); | |
} | |
composeUserHTML(users, updateUsersDOM); | |
}); | |
} | |
$('button').click(updateTwitterUsers); | |
// | |
// Or use Promises to flatten the pyramid of doom | |
// | |
// Using the "Q" API (https://github.com/kriskowal/q) | |
// OR the jQuery Deferred API (http://api.jquery.com/category/deferred-object/) | |
// | |
function updateTwitterUsers() { | |
getTwitterUsers() | |
.then(composeUserHTML) | |
.fail(handleFailure); | |
} | |
$('button').click(updateTwitterUsers); | |
function composeUserHTML(users) { | |
var deferred = Q.defer(), | |
twitterUsersHTML = ''; | |
function getMore() { | |
if (!users.length) { | |
return deferred.resolve(twitterUsersHTML); | |
} | |
var id = users.shift(); | |
getTwitterUser(id, function (err, userData) { | |
if (err) { | |
return deferred.reject(err); | |
} | |
twitterUsersHTML += ('<li>'+ userData +'</li>'); | |
getMore(); | |
}); | |
} | |
getMore(); | |
return deferred.promise | |
} | |
function handleFailure(err) { | |
alert(err.toString()); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment