Created
March 25, 2014 12:05
-
-
Save bennadel/9760469 to your computer and use it in GitHub Desktop.
Writing My First Node.js Module And Event Emitter
This file contains hidden or 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
(function(){ | |
// Define your class. | |
return( Singleton || Constructor ); | |
})(); |
This file contains hidden or 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
// Define our module. | |
(function(){ | |
// Counter variable. | |
var counter = 0; | |
// Return public API. | |
return({ | |
countUp: function(){ | |
return( ++counter ); | |
} | |
}); | |
})(); |
This file contains hidden or 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
// Define our module. | |
// Counter variable. | |
var counter = 0; | |
// Export our public API. | |
exports.countUp = function(){ | |
return( ++counter ); | |
}; |
This file contains hidden or 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
// Include the necessary modules. | |
var sys = require( "sys" ); | |
var http = require( "http" ); | |
// Include the Controller layer. | |
var controller = require( "./controller.js" ); | |
// ---------------------------------------------------------- // | |
// ---------------------------------------------------------- // | |
// Create an instance of the HTTP server. | |
var server = http.createServer( | |
function( request, response ){ | |
// Our controller is an instance of the Event Emitter. We | |
// will be using this to listen to controller events. We | |
// could have handled this with Success and Fail callbacks; | |
// but, I wanted an excuse to experiment with the Event | |
// stuff as it appears to be quite central to Node.js. | |
// Bind to the "Data" event. This is the event that gets | |
// emitted when the controller has data to return. | |
controller.on( | |
"data", | |
function( data ){ | |
// Set the 200-OK header. | |
response.writeHead( | |
200, | |
{ "Content-Type": "text/plain" } | |
); | |
// Return the response from the API request. | |
response.write( JSON.stringify( data ) ); | |
// Close the request. | |
response.end(); | |
} | |
); | |
// Bind to the "Error" event. This is the event that gets | |
// triggered when a success response cannot be returned. This | |
// will either be because an error occurred; or, because the | |
// record request by the API cannot be found. | |
// | |
// NOTE: For our simple purposes, we are going to assume that | |
// the errorType is simply an HTTP Status Code. This will | |
// keep our logic very simple for this exploration. | |
controller.on( | |
"error", | |
function( errorType ){ | |
// Set the error header (is really just an HTTP | |
// status for our purposes). | |
response.writeHead( | |
errorType, | |
{ "Content-Type": "text/plain" } | |
); | |
// Close the request. | |
response.end(); | |
} | |
); | |
// Pass the request off to the controller. | |
controller.handle( request, response ); | |
} | |
); | |
// Point the server to listen to the given port for incoming | |
// requests. | |
server.listen( 8080 ); | |
// ---------------------------------------------------------- // | |
// ---------------------------------------------------------- // | |
// Write debugging information to the console to indicate that | |
// the server has been configured and is up and running. | |
sys.puts( "Server is running on 8080" ); |
This file contains hidden or 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
// Include the event emitter class - the controller is a specialized | |
// instance of the event emitter and will emit the events: | |
// | |
// - data / response | |
// - error / errorType (HTTP Status code) | |
var EventEmitter = require( "events" ).EventEmitter; | |
// Include the Girl Service layer. | |
var girlService = require( "./girl-service.js" ); | |
// ---------------------------------------------------------- // | |
// ---------------------------------------------------------- // | |
// Create an instance of our event emitter. | |
var controller = new EventEmitter(); | |
// Add a handle method to the event emitter instance (controller) | |
// so that the HTTP server will be able to pass the request off for | |
// proper routing. | |
controller.handle = function( request, response ){ | |
// We are going to be looking at urls for RESTful commands. | |
// These will be in the form of: | |
// | |
// NOTE: I am using the term RESTful in the loosest sense. | |
// Really, this is just easy for illustration purposes. | |
// | |
// girls/get | |
// girls/{id}/get | |
// girls/{id}/delete | |
// girls/add/{name} | |
// Define our patterns. | |
var patterns = { | |
getAll: new RegExp( "girls/get", "i" ), | |
getGirl: new RegExp( "girls/(\\d+)/get", "i" ), | |
deleteGirl: new RegExp( "girls/(\\d+)/delete", "i" ), | |
addGirl: new RegExp( "girls/add/([^/]+)", "i" ) | |
}; | |
// Strip off the leading and trailing slashes. | |
var restUri = request.url.replace( | |
new RegExp( "^/|/$", "g" ), | |
"" | |
); | |
// Loop over the patterns to see if any match. | |
for (var patternKey in patterns){ | |
// Try to match the pattern against the URL. | |
if ( match = restUri.match( patterns[ patternKey ] ) ){ | |
// Pass the request off to the service layer. Since | |
// the service layer is performing asynchronous I/O | |
// (theoretically), we need to pass it a callback so | |
// that the service layer can alert us to data events | |
// when they are available. | |
// Build the arguments. Our last argument will always | |
// be a callback for our asynchronous API. In this case, | |
// the callback will be expecting an API response for a | |
// successful call; OR, a null response for a record that | |
// could not be found. | |
var apiArguments = [function( apiResponse ){ | |
// Check to see if we have a valid API response. | |
if (apiResponse){ | |
// The API request was successful - announce | |
// the data event. | |
controller.emit( "data", apiResponse ); | |
} else { | |
// The API request was not successful - announce | |
// the error event. | |
controller.emit( "error", "404" ); | |
} | |
}]; | |
// If there is a captured group in the regex pattern | |
// that we used above, add it as the first argument to | |
// our collection of service-layer invocation arguments. | |
if (match.length > 1){ | |
// Prepend the captured group (an ID) to the list | |
// of arguments used to invoke the service layer. | |
apiArguments.unshift( match[ 1 ] ); | |
} | |
// Invoke the service layer (remember, the last argument | |
// of our invocation array is always the callback for | |
// asynchronous I/O). | |
girlService[ patternKey ].apply( | |
girlService, | |
apiArguments | |
); | |
// The RESTful URL can only match one pattern. | |
// Since we found a match, return out of the request | |
// handler as there is nothing more we can do here | |
// until the data-callback is triggered. | |
return; | |
} | |
} | |
// If we have made it this far, then the incoming request did | |
// not match up with any known API signature. As such, we will | |
// announce (emit) a server error. | |
controller.emit( "error", "500" ); | |
}; | |
// ---------------------------------------------------------- // | |
// ---------------------------------------------------------- // | |
// Expose the controller / event emitter. Since we are exposing | |
// the whole object, rather than just an API interface, we are | |
// redefining the entire exports value. | |
module.exports = controller; |
This file contains hidden or 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
// Create our collection of girls. This collection is keyed by the | |
// ID of the girl. | |
var girls = {}; | |
// Keep a running auto-incrementer. | |
var primaryKey = 0; | |
// ---------------------------------------------------------- // | |
// ---------------------------------------------------------- // | |
// Now, we have to define the exteranl, public API of the module. | |
// Each of the methods in this API gets "exported" as part of the | |
// exposed object. | |
// All of these methods deal with data READS and WRITES. In Node, | |
// this kind of I/O is supposed to be asynchronous; that is, a | |
// method that reads or writes to storage can't return a value | |
// directly (as it is non-blocking... for the most part). Therefore, | |
// we have to pass callbacks to all of our IO methods so that our | |
// calling context can be *alerted* to data events. | |
// I am creating a noop function so I can simplify the logic | |
// surrounding my callbacks. With a noop() (read as No-Op), I can | |
// always have a reference to *a* callback. | |
var noop = function(){}; | |
// I add a girl to the collection. | |
exports.addGirl = function( name, callback ){ | |
// Make sure a callback is defined. | |
callback = (callback || noop); | |
// Create the new girl instance. | |
var girl = { | |
id: ++primaryKey, | |
name: name | |
}; | |
// Add it to the collection. | |
girls[ girl.id ] = girl; | |
// Pass the girl to the callback (the calling context). | |
callback( girl ); | |
// Return this object reference to allow for method chaining. | |
return( this ); | |
}; | |
// I delete the girl with the given ID. | |
exports.deleteGirl = function( id, callback ){ | |
// Make sure a callback is defined. | |
callback = (callback || noop); | |
// Get the girl. | |
var girl = (girls[ id ] || null); | |
// If the girl exists, delete her. | |
if (girl){ | |
delete girls[ girl.id ]; | |
} | |
// Pass the girl to the callback (the calling context). | |
callback( girl ); | |
// Return this object reference to allow for method chaining. | |
return( this ); | |
}; | |
// I return the girl with the given id. | |
exports.getGirl = function( id, callback ){ | |
// Make sure a callback is defined. | |
callback = (callback || noop); | |
// Pass the girl to the callback (the calling context). | |
callback( girls[ id ] || null ); | |
// Return this object reference to allow for method chaining. | |
return( this ); | |
}; | |
// I get all the girls. | |
exports.getAll = function( callback ){ | |
// Make sure a callback is defined. | |
callback = (callback || noop); | |
// Create a holder for our ordered collection. | |
var orderedGirls = []; | |
// Loop over the primary keys to build up the collection | |
// of ordered girls. | |
for ( var i = 1 ; i <= primaryKey ; i++ ){ | |
// Check to see if a girl exists at this key. | |
if (girls[ i ]){ | |
// Add this girl to the result in order. | |
orderedGirls.push( girls[ i ] ); | |
} | |
} | |
// Pass the collection to the callback (the calling context). | |
callback( orderedGirls ); | |
// Return this object reference to allow for method chaining. | |
return( this ); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment