Was reading through code on github, and came across this little gem of a package merge-descriptors by @jonathanong.
The package is just a few lines long, but opened up a world of ideas for me.
function (dest, src) {
Object.getOwnPropertyNames(src).forEach(function (name) {
var descriptor = Object.getOwnPropertyDescriptor(src, name)
Object.defineProperty(dest, name, descriptor)
})
return dest
}
The rest of the article will go into depth about why this package is awesome.
ECMAScript 5 added the ability to define detailed properties. The default behavior of setting a property on an object can be replicated with more explicit syntax.
var person = {};
person.name = 'AJ';
Object.defineProperty(person, 'name', {
writeable: true,
configurable: true,
enumerable: true,
value: 'AJ'
});
Writable means the property value can be changed via assignment.
person.name = 'Not AJ';
Configurable means it can be deleted, changed, and the property settings can be changed as well.
delete person.name;
Object.defineProperty(person, 'name', { /* changes */ });
Enumerable means that the property shows up when iterating.
for (var key in person) {
console.log(key, person[key]);
}
You can get the property settings for any property on any object.
Object.getOwnPropertyDescriptor(person, 'name');
That's just default behavior though.
Properties can also have custom getters and setters. This allows for cool things like lazy attributes. For example, only parsing a request body if necessary.
function Request (bodyString) {
this.bodyString = bodyString;
}
Object.defineProperty(Request.prototype, 'bodyJSON', {
get: function () {
if (this._bodyJSON === undefined) {
this._bodyJSON = JSON.parse(this.bodyString);
}
return this._bodyJSON;
}
});
var req = new Request('{ "name": "AJ" }');
req.bodyJSON.name; // 'AJ'
For a setter, we can make sure the request method is always all caps.
Object.defineProperty(Request.prototype, 'method', {
writeable: true,
enumerable: true,
set: function (method) {
this._method = method.toUpperCase();
},
get: function () {
return this._method;
}
});
req.method = 'post';
req.method; // 'POST'
The above code doesn't look so good. Fortunately there's nicer syntax available.
var mixins = {};
var table = {
ok: 200,
created: 201,
accepted: 202,
// ...
};
// reverse lookups too
Object.keys(table).forEach(function (key) {
table[ table[key] ] = key;
});
mixins.HumanStatus = {
get status () {
return table[this.statusCode];
},
set status (type) {
this.statusCode = table[type];
}
}
Now suppose we called the function written by Jonathan at the very beginning of the article function mixin
.
function Response () {}
mixin(Response.prototype, mixins.HumanStatus);
var res = new Response;
res.statusCode = 200;
res.status; // 'ok'
res.status = 'created';
res.statusCode; // 201
I haven't looked too deeply into javascript ORMs, but if I were writing one, I would provide then-able associations through getters. Basically augmented promises.
For example, consider a find
static method that returns a promise. If you call then
on the promise, the user is eventually returned.
var userPromise = User.find(id);
userPromise.then(function (user) {
// user has been fetched from db
});
But you could also add a getter to the user promise to return a new promise for associated posts.
var postsPromise = userPromise.posts;
postsPromise.then(function (posts) {
// posts belonging to user fetched from db
});
Cool I guess. How about some methods on that new posts promise for chainable querying?
postsPromise.order({ created_at: -1 }).limit(10).then(function (recentPosts) {
// recent posts fetched from db
});
If you use a web framework that resolves promises for you implicitly (like Koa) then your asynchronous code could actually look and feel like synchronous code.
function *() {
var user = User.find(id);
var posts = user.posts.limit(5);
yield { user: user, posts: posts, count: posts.count };
}
So, yeah, dynamic properties!