I've got a complex Node SDK project that uses some class inheritance to try and static-ify Javascript. I'm using Node's module caching behavior to create a singleton-like behavior for the SDK (the Project
class, a shared instance of ProjectClient
). To init, it looks like this:
var Project = require('./project'),
Project.init(params)
// Project is now an instance of ProjectClient
I've got some object type classes for data as well: Entity
(a standard parsed JSON payload object) and User
(a special type of Entity that contains user properties).
The ProjectClient
class has several methods that allow RESTful API calls to occur, eg Project.GET()
, Project.PUT()
. These work just fine when instantiating the Project
"singleton".
I'm now trying to create convenience methods attached to Entity
that will leverage ProjectClient
's RESTful operations, eg Entity.save()
, Entity.refresh()
.
When I try to import Project
into the Entity
:
var Project = require('../project')
I get:
TypeError: The super constructor to `inherits` must have a prototype.
at Object.exports.inherits (util.js:756:11)
Troubleshooting leads me to this being related to util.inherits(ProjectUser, ProjectEntity)
in User
, because if I comment it out, I get this instead:
Uncaught TypeError: ProjectEntity is not a function
What's going on with inherits
? why does it think that Entity
doesn't have a prototype? My best guess is that it's related to the fact that I'm recursively nesting modules within other modules (bad, I know), but I've even tried doing something like this in the various classes but to no avail:
module.exports = _.assign(module.exports, **ClassNameHere**)
Here's some reduced code for each class:
var Project = require('../Project'),
_ = require('lodash')
var ProjectEntity = function(obj) {
var self = this
_.assign(self, obj)
Object.defineProperty(self, 'isUser', {
get: function() {
return (self.type.toLowerCase() === 'user')
}
})
return self
}
module.exports = ProjectEntity
var ProjectEntity = require('./entity'),
util = require('util'),
_ = require('lodash')
var ProjectUser = function(obj) {
if (!ok(obj).has('email') && !ok(obj).has('username')) {
// This is not a user entity
throw new Error('"email" or "username" property is required when initializing a ProjectUser object')
}
var self = this
_.assign(self, ProjectEntity.call(self, obj))
return self
}
util.inherits(ProjectUser, ProjectEntity)
module.exports = ProjectUser
'use strict'
var ProjectClient = require('./lib/client')
var Project = {
init: function(options) {
var self = this
if (self.isInitialized) {
return self
}
Object.setPrototypeOf(Project, new ProjectClient(options))
ProjectClient.call(self)
self.isInitialized = true
}
}
module.exports = Project
var ProjectUser = require('./user'),
_ = require('lodash')
var ProjectClient = function(options) {
var self = this
// some stuff happens here to check options and init with default values
return self
}
ProjectClient.prototype = {
GET: function() {
return function() {
// async GET request with callback
}
},
PUT: function() {
return function() {
// async PUT request with callback
}
}
}
module.exports = ProjectClient
So, as you have correctly deducted there's a problem with a circular dependency. Your Entity
module requires the Project
Module which requires the Client
module which requires the User
Module which requires the Entity
Module.
There's something you can do about it but it will depend on your starting point. If you require the Project
module first then it should work with the code provided because the Entity
module doesn't do anything with the Project
module. Nothing has been exported on that module, so it's just an empty object. Then again, any source of errors on that module would be related on whatever exported objects are depended inside that module. So if you'd need the object with init
inside Entity
then there would be a problem.
You can export some methods/functions before initializing the dependency chain, which will make them available by then. Take the example of NodeJS
docs:
a.js
console.log('a starting');
exports.done = false;
var b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');
b.js
console.log('b starting');
exports.done = false;
var a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');
main.js
console.log('main starting');
var a = require('./a.js');
var b = require('./b.js');
console.log('in main, a.done=%j, b.done=%j', a.done, b.done);
So, main.js
is the starting point. It requires a.js
which immediately exports a done
property and then requires b.js
. b.js
also exports a done
property. The next line requires a.js
which doesn't load a.js
again but returns the exported properties so far (which includes the done
property). At this point, a
is incomplete but it has managed to give b
enough to continue working. It's next line (on b.js
) will print the exported property (a.done, which is false), then reset the exported property done
to true. We are back on a.js
in the require('b.js')
line. b.js
is now loaded completely and the rest is pretty easy to explain.
Output is:
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done=true, b.done=true
Here's the example, just in case you want to read the official docs.
Right, so point being... what can you do?
It's possible to export some things before initializing the dependency cycle, just so long as you don't actually need those dependencies. For example you can:
a.js
exports.func = function(){ console.log('Hello world'); }
var b = require('./b.js');
console.log('a done');
b.js
var a = require('./a.js');
a.func(); //i'm still incomplete but i got func!
console.log('b done');
You can't:
a.js
b.func(); //b isn't even an object yet.
var b = require('./b.js');
console.log('a done');
b.js
exports.func = function(){ console.log('hello world'); }
var a = require('./a.js');
a.func();
console.log('b done');
However, if your a.js
module only exports functions then there's no real problem as long as these functions aren't being called somewhere else:
a.js
exports.func = function(){ b.func(); }
var b = require('./b.js');
console.log('a done');
b.js
exports.func = function(){ console.log('hello world'); }
var a = require('./a.js');
console.log('b done');
You are exporting a function that uses b
, the function doesn't need to know about b
right then, only when it's called. So both modules are being loaded correctly. If you are just exporting functions there's no problem with having the dependencies declared afterwards.
So you can require a.js
from your main point and func
will work correctly as the b
reference points now to the complete b.js
module. You can follow this pattern so long as you don't use the functions you export when loading the dependencies.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.