brandonscript
brandonscript

Reputation: 73005

Add 'fake' singleton wrapper to Node class with mandatory initializer method

(Disclaimer: Deleted previous question that led to some new fundamental problems, so trying again with a more sensible question).

I've got a class that has some required initialization properties:

var CustomRequest = require('./request'),
    ok = require('objectkit'),
    helpers = require('../helpers')

var MySDKClient = function(orgId, appId) {
    var self = this
    if (ok(orgId).exists() && ok(appId).exists()) {
        self.orgId = orgId
        self.appId = appId
        return self
    } else {
        throw new Error('orgId and appId must be passed during initialization')
    }
}

MySDKClient.prototype = {
    GET: function(uri, options, callback) {
        return new CustomRequest('GET', uri, options, callback)
    }
}

// Exports
module.exports = MySDKClient

This works as-is like so:

var MySDKClient = require('./lib/client')
var client = new MySDKClient('orgId', 'appId')

But I need to wrap this in a new class the holds a single instance of MySDKClient and inherits all of its methods/properties. We'll call it MySDK, and it needs to have a custom initializer method:

var MySDK = require('./mysdk')
MySDK.initialize('orgId', 'appId')
module.exports = MySDK

(Or less preferred):

var MySDK = require('./lib/client').initialize('orgId', 'appId')
module.exports = MySDK

I want it to fail to initialize (and therefore fail to call any of MySDKClient's methods) by way of the throw in MySDKClient if the initialize method has not been called.

For example, this should work:

var MySDK = require('./lib/client')
MySDK.initialize('orgId', 'appId')
MySDK.GET('...') // should work!

And this should not:

var MySDK = require('./lib/client')
MySDK.GET('...') // should throw error because of 'throw new Error()` 
                 // that already exists in MySDKClient -- more accurately, 
                 // MySDK should be null or undefined because it wasn't initialized.

How would I do this? I've tried using util.inherits() and Object.create() to create a 'subclass', but no matter what I do, the prototype methods from the MySDKClient class either aren't accessible, or they work whether or not the client has been properly initialized.


Update: Trying out @sg.cc's solution, MySDKClient behaves just fine more/less like it did before, but I still can't init the MySDK instance in a module properly:

// mysdk.js

var MySDKClient = require('./lib/client'),
    ok = require('objectkit'),
    helpers = require('./helpers'),
    util = require("util")

function MySDK() {
    // new MySDKClient?
}

MySDK.prototype = {
    initialize: function(orgId, appId) {
        return new MySDKClient(orgId, appId)
    }
}

// Should I still be doing this?
// util.inherits(MySDK, MySDKClient)

// Exports
module.exports = new MySDK

Calling it:

var MySDK = require('./mysdk')
MySDK.initialize('peter', 'wolf')
MySDK.GET() // undefined is not a function

Upvotes: 0

Views: 77

Answers (2)

lorefnon
lorefnon

Reputation: 13105

You need to stop thinking in terms of classes and start embracing the prototypal inheritance of javascript. Also please try to simplify the code snippets to the bare minimum to make it easy to reproduce.

I have simplified your example for reproducibility but the following should point you to the right direction:

Here is my simplified MySDKClient constructor:

var MySDKClient = function(orgId) { this.orgId = orgId; } 
MySDKClient.prototype.GET = function() { alert(this.orgId); }

Your MySDK does not have to be a constructor (or a class if you prefer):

var MySDK = { 
  initialize: function(orgId) { 
    Object.setPrototypeOf(MySDK, new MySDKClient(orgId)) 
  } 
}

Try it out in the console:

MySDK.GET()

Will throw an error because when the interpreter walks up the prototype chain of MySDK it does not find GET function.

Now if you call MySDK.initialize(10); the prototype chain of the object is modified to encorporate MySDKClient. And now if you run MySDK.GET() you will see 10 being alerted.

While Object.setPrototypeOf is a new addition to javascript, there is a very simple polyfill available using Object.prototype.__proto__ that work on pretty much all major platforms

Upvotes: 2

sg.cc
sg.cc

Reputation: 1826

var CustomRequest = require('./request'),
  ok = require('objectkit'),
  helpers = require('../helpers')   



var MySDKClient = function(org, app){
  return (function init(orgId, appId) {
    var init = false;
    if( ok(orgId).exists() && ok(appId).exists() ) {
      this.orgId = orgId;
      this.appId = appId;
      init = true;
    } else {
      throw new Error('orgId and appId must be passed during initialization')
    }
    if( !init ) return;
    return {
      GET: function(uri, options, callback){
        // Where are you using orgId that customRequest needs to fail if they aren't present?
        return new CustomRequest('GET', uri, options, callback);
      }
    }
  })(org, app);
}

module.exports = MySDKClient;

To test:

var MySDK = require('./lib/client');

var a = MySDK(123, abc);
var b = MySDK(); // -> Exception thrown
a.GET(some, args, here); // Works
b.GET(some, args, here); // property GET of undefined is not a function

Upvotes: 1

Related Questions