i.js
v0.0.1
Published
JavaScript interface assertion library
Downloads
2
Readme
I.js
JavaScript library that provides support for enforcing objects to implement given interfaces.
Motivation
JavaScript does not support interfaces natively. Often though, we do want to be able to determine that an object (usually one passed as a parameter to a function) has some given methods on it, and also conditionally call a method on an object if it has the method available.
To do this, we end up with lots of boilerplate code, such as:
function foo(bar) {
if (typeof bar.someMethod !== 'function' ||
typeof bar.anotherMethod !== 'function') {
throw new TypeError('bar must implement the someMethod and anotherMethod methods.');
}
}
and
function foo(bar) {
if (typeof bar.someMethod === 'function') {
bar.someMethod();
}
if (typeof bar.anotherMethod === 'function') {
bar.anotherMethod();
}
}
Finally, as JavaScript developers we're now plugging together more and more 3rd party modules, and knowing how to use the APIs exposed to us becomes more important. Currently, we have to read documentation (maybe above the function, maybe at the top of the file in an @description
docblock, maybe on the web), read the first few lines of a method if the expectations are helpfully asserted up front (like the first example above), or sometimes just dig through the errors that our console throws up as we hit points in the code that expect an object to have a particular shape.
This is pretty much what interfaces in other programming languages are for. Although we can't make interfaces enforceable through static analysis like in other languages, we can do a lot better than what we currently have.
The solution
i.js gives us an implementation of interfaces in JavaScript. It lets us:
- declare our interfaces in a standardised way, in their own file, and with their own documentation.
- check an object to ensure it conforms to an interface, throwing descriptive and consistent errors if not.
- conditionally call methods on an object without code bloat.
- modify an object so that it does conform to an interface, so we can knowingly just call methods on it, again saving code bloat.
The API
Creating an interface
var I = require('i');
var myInterface = new I({
required: {
requiredMethod1: I.function,
requiredMethod2: I.function
},
optional: {
optionalMethod1: I.function,
optionalMethod2: I.function
}
});
var interfaceWithOnlyRequiredMethods = new I(['requiredMethod1', 'requiredMethod2']);
Using an interface
function mustPassProperObject(object) {
myInterface.check(object);
// if we got here, we can call these methods safely
object.requiredMethod1();
object.requiredMethod2();
}
// throws a TypeError, mentioning the missing methods.
mustPassProperObject({});
function callOptionalMethods(object) {
// makes optional methods available
myInterface.complete(object);
// object will have these methods added
myInterface.optionalMethod1();
myInterface.optionalMethod2();
}
// will not throw a TypeError, as optional methods are added to the object
callOptionalMethods({});
function checkBeforeCalling(object) {
myInterface.tryCall(object, 'optionalMethod1', arg1, arg2);
myInterface.tryApply(object, 'optionalMethod2', [arg1, arg2]);
// this will throw an error, as tryCall does not guard against required method calls
myInterface.tryCall(object, 'requiredMethod1');
}
// will throw a TypeError because requiredMethod1 does not exist
checkBeforeCalling({});
Notes
Optional methods
On the face of it, there's not a lot of point in delcaring optional methods in an interface. However, what they do provide is documentation, effectively stating "here is some functionality you can use if you want, but you don't have to." These are extensively used in Apple's Objective-C libraries, most often for delegate callback methods, and probably in other libraries, too.
The use of I.function in interface declarations
This is primarily there because at some point in the future we might want to assert other property types, such as Number, Boolean, and even deeply-nested object structures.
Future work
I would like to add more features, not necessarily in this order, to i.js. I would like to be able to:
- declare that an object must have a primitive as a property (e.g. Number or String)
- declare that an object must have an object that implements another interface as a property (nested interfaces)
- declare an interface that extends another interface
- allow objects to be restored to their original state after having been
complete
d - expose
tryCall
andtryApply
as methods on the I object, so that I can guard against method calls even without using an interface - support AMD and
window
global variable so that I can use i.js in other environments
License
i.js is released under the MIT License.