Jonathan Hall
Jonathan Hall

Reputation: 79594

How to "subclass" a node.js module

I've been primarily a Perl coder for years, but also have a background in C++, so I'm coming from a "classical" OO background, and now learning node.js. I just read through The Principles of Object-Oriented JavaScript, which did a good job of explaining the JS concept of OO to classically-minded people like me. But I'm left with a question, specifically related to node.js and inheritance. Pardon me if I'm still using "classical" vocabulary to explain my problem.

Lets suppose I have a module lib/foo.js:

function foo() {
    console.log('Foo was called');
}

module.exports.foo = foo;

And I want to "subclass" this in another module lib/bar.js:

var foo = require('foo.js');

// Do some magic here with *.prototype, maybe?

function bar() {
    console.log('Bar was called');
}

module.exports.bar = bar;

Such that my main script can do this:

var bar = require('lib/bar.js');

bar.foo(); // Output "Foo was called"
bar.bar(); // Output "Bar was called"

Is this even possible? If so, what am I missing?

Or is this an anti-pattern? Or plain impossible? What should I do instead?

Upvotes: 2

Views: 3666

Answers (2)

Jonathan Hall
Jonathan Hall

Reputation: 79594

For reference, the specific solution I came up with to my sample code problem follows:

In lib/foo.js:

var Foo = function() {}

Foo.prototype.foo = function() {
    console.log('Foo was called!');
};

module.exports = new Foo;

In lib/bar.js:

var foo = require('./foo.js');

var Bar = function() {}

Bar.prototype = Object.create(foo.__proto__);
Bar.prototype.constructor = Foo;

Bar.prototype.bar = function() {
    console.log('Bar was called!');
};

module.exports = new Bar;

Then in my test script:

var bar = require('lib/bar.js');

bar.foo(); // Output "Foo was called"
bar.bar(); // Output "Bar was called"

Upvotes: 0

user949300
user949300

Reputation: 15729

Here's how I did it, to override one method in the request module. Warning: many node modules are poorly designed for extension, including request, as they do way too much stuff in the constructor. Not just a gazillion argument options, but starting up IO, connections, etc. For example, request does the http connection (eventually) as part of the constructor. There is no explicit .call() or .goDoIt() method.

In my example, I wanted to use querystring instead of qs to format forms. My module is cleverly named "MyRequest". In a separate file named myrequest.js you have:

var Request = require('request/request.js');
var querystring = require('querystring');

MyRequest.prototype = Object.create(Request.prototype);
MyRequest.prototype.constructor = MyRequest;

// jury rig the constructor to do "just enough".  Don't parse all the gazillion options
// In my case, all I wanted to patch was for a POST request 
    function MyRequest(options, callbackfn) {
        "use strict";
        if (callbackfn)
           options.callback = callbackfn;
        options.method = options.method || 'POST';          // used currently only for posts
        Request.prototype.constructor.call(this, options);  
           // ^^^  this will trigger everything, including the actual http request (icky)
           // so at this point you can't change anything more
    }

    // override form method to use querystring to do the stringify
    MyRequest.prototype.form = function (form) {
     "use strict";
        if (form) {
            this.setHeader('content-type', 'application/x-www-form-urlencoded; charset=utf-8');
            this.body = querystring.stringify(form).toString('utf8');
  // note that this.body and this.setHeader are fields/methods inherited from Request, not added in MyRequest.
            return this;
        }

        else
           return Request.prototype.form.apply(this, arguments);
    };

Then, in your application, instead of

var Request = require("request");
Request(url, function(err, resp, body)
  {
      // do something here
   });

you go

var MyRequest = require("lib/myrequest");
MyRequest(url, function(err, resp, body)
  {
      // do that same something here
   });

I'm not a JavaScript guru so there may be better ways...

Upvotes: 2

Related Questions