A function that accepts: a string or a function or two objects

I'm creating a module in Node.js that exposes only one function named direct, basically a request router (Yes, I'm making my own, in an effort to learn). However, I'd like to simplify the API to just use one function. Everything else comes chained after, depending on what was direct-ed.

It will be accepting 3 types of input for now: A string (route) or a function (a callback) or two objects - the request and response objects from http.createServer:

direct('/');           //pass a route string
direct(function(){});  //pass callback
direct(req,res);       //pass the request and response

Internals for these are mine to worry. Currently I'm doing:

//if only one, 
if(arguments.length === 1) {
    if( typeof arguments[0] === 'string') {
        //add to routes
    } else if( typeof arguments[0] === 'function') {
        //add to callbacks
    } else {
        //return an error
    }
} else if(arguments.length === 2 && ...check if both are typeof object, not null, not instance of array...) {
    //extremely long check if both are objects
    //planning to extract the check as a function
} else {
    //return an error object
}

As you see, it seems like I am hard-coding much of the stuff. Also, the checks are inefficient and a bit long.

  • What's an efficient way of filtering the arguments according to the given criteria?
  • Is there a way to check if the objects sent in was the request and response objects of the http.createServer?

if you implement direct like this it might be easier:

direct = function(o){

  if(o['route'])
    DO SOMTHING
  }

  if(o['fn']){
    DO SOMETHING ELSE
  }
  ...
}

always get a configuration object that will hold the intent of the caller.

You could use a lookup table based on typeof:

var handlers = {
    'string':   { n: 1, fn: function(route)     { ... } },
    'function': { n: 1, fn: function(callback)  { ... } },
    'object':   { n: 2, fn: function(req, resp) { ... } }
};

and then:

var handler = handlers[typeof arguments[0]];
if(!handler) {
    // throw a hissy fit and bail out
}
if(arguments.length != handler.n) {
    // throw a different hissy fit and bail out.
}
return handler.fn.apply(null, arguments);

You could also ditch n and use the length property of the handler functions:

var handlers = {
    'string':   function(route)     { ... },
    'function': function(callback)  { ... },
    'object':   function(req, resp) { ... }
};

var handler = handlers[typeof arguments[0]];
if(!handler) {
    // throw a hissy fit and bail out
}
if(arguments.length != handler.length) {
    // throw a different hissy fit and bail out.
}
return handler.apply(null, arguments);

You could further abstract the handler.n check into a function and then the 'object' version of the checker could use instanceof to make sure req and resp are the right sorts of things. This would make your direct look something like this:

var handlers = {
    'string': {
        good_args: function(arguments) { ... },
        fn: function(route) { ... }
    },
    //...
};

var handler = handlers[typeof arguments[0]];
if(!handler) {
    // throw a hissy fit and bail out
}
if(!handlers.check_args(arguments)) {
    // throw a different hissy fit and bail out.
}
return handler.fn.apply(null, arguments);

If you need to handle more complexity and variability then you could replace the simple objects in handlers with "real" objects that support a known interface. That smells like over-engineering though, I'd probably stop at the good_args version.

You're pretty much building a miniature command interpreter, might as well make it look like one.

  • Is there a way to check if the objects sent in was the request and response objects of the http.createServer?

This part is easy.

From the docs:

request is an instance of http.ServerRequest and response is an instance of http.ServerResponse

So your code would look like this, assuming that your direct function takes named formal parameters a and b (I think this is more readable that arguments[0] and arguments[1]):

else if (arguments.length == 2
        && a instanceof http.ServerRequest
        && b instanceof http.ServerResponse)

I think like you, there are functions that are more elegant and more easy to use if you just pass to them the data.

Your code is correct but you are too strict. Javascript is a weakly typed language. If you begin to check the type of each parameter you'll never end and one line functions could become too complex. If you say in your docs that the function receives 2 strings then the user must pass 2 strings, it'n not your problem if he decides to pass one boolean and one function.

I'll write the following:

var direct = function (param, res){
  var type = typeof param;
  if (type === "string"){
    //direct("/")
  }else if (type === "function"){
    //direct(function(){})
  }else{
    //direct(req, res)
  }
};