Skip to content

Instantly share code, notes, and snippets.

@kevinswiber
Last active February 15, 2020 14:39
Show Gist options
  • Save kevinswiber/6452906 to your computer and use it in GitHub Desktop.
Save kevinswiber/6452906 to your computer and use it in GitHub Desktop.
Quick service locator in JavaScript.
var ServiceLocator = require('./service_locator');
var locator = new ServiceLocator();
var Greeter = function(message) {
this.message = message;
};
Greeter.prototype.greet = function() {
console.log(this.message);
};
var greeterFn = function(message) { console.log(message); };
var greeterObj = {
greet: function(message) {
console.log(message);
}
};
locator.register('greeter1', Greeter, ['1: Hello world!']);
locator.register('greeter2', greeterFn, ['2: Hello world!']);
locator.register('greeter3', greeterObj);
for (var i = 0; i < 3; i++) {
console.log('iteration:', i);
locator.resolve('greeter1').greet();
locator.resolve('greeter2');
locator.resolve('greeter3').greet('3: Hello world!');
console.log('');
}
/*
Output:
iteration: 0
1: Hello world!
2: Hello world!
3: Hello world!
iteration: 1
1: Hello world!
2: Hello world!
3: Hello world!
iteration: 2
1: Hello world!
2: Hello world!
3: Hello world!
*/
var ServiceLocator = require('./service_locator');
var locator = new ServiceLocator();
var Greeting = function(message) {
this.message = message;
};
var Greeter = function(greeting) {
this.greeting = greeting;
};
Greeter.prototype.greet = function() {
return this.greeting.message;
};
var Printer = function(greeter) {
this.greeter = greeter;
};
Printer.prototype.print = function() {
console.log(this.greeter.greet());
};
// Demo 1: Basic register/resolve behavior and component dependencies.
locator.register('greeting', Greeting, ['Hello world!']);
locator.register('greeter', Greeter, [locator.component('greeting')]);
locator.register('printer', Printer, [locator.component('greeter')]);
var printer;
printer = locator.resolve('printer');
printer.print();
// Output: Hello world!
///////////////////////////
// Demo 2: Transient lifestyle behavior and dynamic dependencies.
var now = function() {
return 'The time is: ' + new Date();
};
locator.register('greeting', Greeting, [locator.dynamic(now)], 'transient');
locator.register('greeter', Greeter, [locator.component('greeting')], 'transient');
locator.register('printer', Printer, [locator.component('greeter')], 'transient');
printer = locator.resolve('printer');
printer.print();
setTimeout(function() {
printer = locator.resolve('printer');
printer.print();
}, 3000);
// Sample Output:
//
// The time is: Thu Sep 05 2013 14:53:58 GMT-0400 (EDT)
// The time is: Thu Sep 05 2013 14:54:01 GMT-0400 (EDT)
//
//////////////////////////
var Implementation = function(constructor, args) {
this.constructor = constructor;
this.args = args;
};
var Definition = function(name, constructor, args, lifestyle) {
this.name = name;
this.value = new Implementation(constructor, args);
this.dependencies = [];
this.lifestyle = lifestyle || 'singleton';
this.instance = null;
};
var ServiceLocator = module.exports = function() {
this.entries = {};
this.dependents = {};
};
ServiceLocator.prototype.register = function(name, constructor, args, lifestyle) {
var definition = new Definition(name, constructor, args, lifestyle);
args = args || [];
var self = this;
args.forEach(function(arg) {
if (typeof arg === 'object' && arg['$type']
&& arg['$type'] === 'component') {
var $name = arg['$name'];
definition.dependencies.push($name);
if (!self.dependents[$name]) {
self.dependents[$name] = [];
}
self.dependents[$name].push(definition.name);
}
});
this.evict(name);
this.entries[name] = definition;
};
ServiceLocator.prototype.evict = function(name) {
var self = this;
if (self.dependents[name]) {
self.dependents[name].forEach(function(dep) {
self.entries[dep].instance = null;
self.evict(dep);
});
}
};
ServiceLocator.prototype.resolve = function(name) {
var definition = this.entries[name];
if (!definition) {
var message = 'No definition for `' + name + '` exists.';
throw new Error(message);
}
var obj;
if (definition.instance) {
if (typeof definition.instance === 'function') {
obj = definition.instance();
} else if (typeof definition.instance === 'object') {
obj = definition.instance;
}
} else {
var value = definition.value;
var constructor = value.constructor;
var tempArgs = value.args || [];
var args = [];
var self = this;
tempArgs.forEach(function(arg) {
if (typeof arg === 'object' && arg['$type']) {
var type = arg['$type'];
if (type === 'component') {
arg = self.resolve(arg['$name']);
definition.dependencies.push(arg['$name']);
} else if (type === 'dynamic') {
arg = arg['$fn']();
}
}
args.push(arg);
});
if (typeof constructor === 'function') {
obj = Object.create(constructor.prototype);
obj.constructor.apply(obj, args);
if (!Object.keys(constructor.prototype).length &&
!Object.keys(obj).length) {
// non-constructor function
// bind args to function for entry instance
var boundArgs = [constructor].concat(args);
if (definition.lifestyle === 'singleton') {
definition.instance = constructor.bind.apply(constructor, boundArgs);
}
} else {
if (definition.lifestyle === 'singleton') {
definition.instance = obj;
}
}
} else if (typeof constructor === 'object') {
obj = constructor;
if (definition.lifestyle === 'singleton') {
definition.instance = obj;
}
}
}
return obj;
};
ServiceLocator.prototype.component = function(name) {
return {
$type: 'component',
$name: name
};
};
ServiceLocator.prototype.dynamic = function(fn) {
return {
$type: 'dynamic',
$fn: fn
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment