2012年3月21日星期三

A way to expose signleton object and its constructor in node.js

In Node.js world, we usually encapsulate a service into a module, which means the module need to export the façade of the service. In most case the service could be a singleton, all apps use the same service.
But in some rare cases, people might would like to create several instances of the service ,which means the module also need to also export the service constructor.

A very natrual idea is to export the default service, and expose the constructor as a method of the default instance. So we could consume the service in this way:

var defaultService = require('service');
var anotherService = service.newService();

So we need to write the module in this way:

function Service() { }

module.exports = new Service();
moudle.exports.newService = Service;

But for some reason, node.js doesn't allow module to expose object by assigning the a object to module.exports. 
To export a whole object, it is required to copy all the members of the object to moudle.exports, which drives out all kinds of tricky code.
And things can become much worse when there are backward reference from the object property to itself.

So to solve this problem gracefully, we need to change our mind.
Since it is proved that it is tricky to export a object, can we try to expose the constructor instead? 
Then answer is yes. And Node.js does allow we to assign a function to the moudle.exports to exports the function. 
So we got this code.

function Service() { }
module.exports = Service;

So we can use create service instance in this way:

var Service = require('service');
var aService = new Service();

As you see, since the one we exported is constructor so we need to create a instance manually before we can use it. Another problem is that we lost the shared instance between module users, and it is a common requirement to share the same servcie intance between users.

How to solve this problem? Since as we know, function is also kind of object in javascript, so we can kind of add a memeber to the constructor called default, which holds the shared instance of the service.
This solution works but not in a graceful way! A crazy but fansy idea is that can we transform the constructor itsself into kind of singleton instance??!! Which means you can do this:

var defaultService = require('service');
defaultService.foo();
var anotherService = service();
anotherService,foo();

The code style looks familiar? Yes, jQuery, and many other well-designed js libraries are designed to work in this way. 
So our idea is kind of feasible but how?
Great thank to Javascript's prototype system (or maybe SELF's prototype system is more accurate.), we can simply make a service instance to be the constructor's prototype.

function Service() { }
module.exports = Service;
Service.__proto__ = nw Serivce;

Sounds crazy, but works, and gracefully! That's the bueaty of Javascript. 

Best regards, 

TimNew
-----------
If not now then when?
If not me then who?

Release your passion
To realize your potential

Sent with Sparrow

Posted via email from 米良的实验室

2012年3月18日星期日

Enhanced typeof() operator in JavaScript

Javascript is weakly typed, and its type system always behaves different than your expectation.

Javascript provide typeof operator to test the type of a variable. it works fine generally. e.g.
typeof(1) === 'number'
typeof('hello') === 'string'
typeof({}) === 'object'
typeof(function(){}) === 'function'

But it is not enough, it behaves stupid when you dealling with objects created by constructors. e.g.
if you expected typeof(new Date('2012-12-12')) === 'date', then you must be disappointed, since actually typeof(new Date('2012-12-12')) === 'object'.
Yes, when you apply typeof() opeartor on any objects, it just yield the general type "object" rather than the more meaningful type "date".

How can we make the typeof() operator works in the way as we expected?
As we know when we create a object, the special property of the object constructor will be set to the function that create the object. which means:
(new Date('2012-1-1')).constructor === [Function Date]
So ideally we can retrieve the name of the function as the type of the variable. And to be compatible with javascript's native operator, we need to convert the name to lower case. So we got this expression
(new Date('2012-1-1')).constructor.name.toLowerCase() === 'date'
And luckily, we can also apply this to other primitive types, e.g:
(123).constructor.name.toLowerCase() === 'number'
('hello').constructor.name.toLowerCase() === 'string'
(function(){}).constructor.name.toLowerCase() === 'function'
or even
({}).constructor.name.toLowerCase() === 'object'
So in general, we use this expression as new implementation of the typeof() operator! EXCEPT One case!

If someone declare the object constructor in this way, our new typeof() implementation will work inproperly!

var SomeClass = (function() {
return function() {
this.someProperty='some value';
}
})();

or even define the constructor like this

var SomeClass = function() {
this.someProperty = 'some value';
}

And we will find that 
(new SomeClass).constructor.name.toLowerCase() === ''
the reason behind this is because the real constructor of the SomeClass is actually an anonymous function, whose name is not set.

To solve this problem, we need to declare the name of the constructor:
var SomeClass = (function() {
return function SomeClass() {
this.someProperty='some value';
}
})();

or even define the constructor like this

var SomeClass = function SomeClass() {
this.someProperty = 'some value';
}

Best regards,

TimNew
-----------
If not now then when?
If not me then who?

Release your passion
To realize your potential

Sent with Sparrow

Posted via email from 米良的实验室

2012年3月15日星期四

Distribute files to multiple servers via scp

The most common task when operating the servers is to distribute a file to multiple servers.

So I wrote a piece of shell script to solve this problem:

  1. #!/bin/bash  
  2. echo "mscp <source file> <target dir>"  
  3. SourceFile=$1  
  4. TargetDir=$2  
  5. echo "Copy $SourceFile to $TargetDir as $RemoteUser"  
  6. echo "Enter the servers:"  
  7. if [ -f $SourceFile ]  
  8. then  
  9.   printf "File found, preparing to transfer\n"  
  10.   while read server  
  11.   do  
  12.   scp -p $SourceFile ${server}:$TargetDir  
  13.   done  
  14. else  
  15.   printf "File \"$SourceFile\" not found\n"  
  16.   exit 1  
  17. fi  
  18. exit 0  

 

call the script "mscp <source file> <target dir>", then the script will ask you the list of target servers. So you can type them one by one. If the remote user is different than you current user, you can also explicitly identify it by typeing user@server

 

Beside the previous scenario, there is a more common sceanrio, that you have got a server list stored in afile already. Then instead of type the servers line by line, you can pipe the file content to the script.

e.g: cat server_list.txt > mscp src_files dest_path

Best regards,

TimNew
-----------
If not now then when?
If not me then who?

Release your passion
To realize your potential

Sent with Sparrow

Posted via email from 米良的实验室