I'm writing this question after 2 days of total struggling during which I couldn't find a solution but also couldn't find an explanation of why this code is not working.
I will report a super simplified simulation of my code.
I have 5 Node.js files:
1) server.js -> Is the main file used to start the server
2) globalVars.js -> Is where all the server "global" objects are stored.
3) globalFunctions.js -> Is where all "general" functions are stored to be used by all other modules
4) helloManager.js -> Example file which contains some standard function used by the server
5) aspect.js -> Same as helloManager.js
//server.js
//Loading the globalVars file. All objects are passed by reference so I use this to store the global variables
var globalVars = require("./globalVars.js");
//Assigning to the respective global object all the functions exported from other modules
globalVars.Modules.globalFunctions = require("./globalFunctions.js");
globalVars.Modules.aspect = require("./aspect.js");
globalVars.Modules.helloManager = require("./helloManager.js");
//After this point, the objects in globalVars.js will be populated with the respective functions exported from the files
//A timeout just to be sure it is not a problem of timing? (Well, it is not...)
setTimeout(function(){
console.log(globalVars.Modules.helloManager.helloOutput());
}, 2000);
/*
Console will throw the following error:
../globalFunctions.js:6
return "MR. " aspect.getAspect();
^
TypeError: aspect.getAspect is not a function
*/
//globalVars.js
//Objects that will be populated with the functions inside other modules
module.exports.Modules = {
aspect: {},
helloManager: {},
globalFunctions: {}
};
//helloManager.js
var globalVars = require("./globalVars.js");
var { globalFunctions } = globalVars.Modules;
module.exports.helloOutput = function(){
return "hello " globalFunctions.getHumanData();
};
//aspect.js
module.exports.getAspect = function(){
return "human";
};
//globalFunctions.js
var globalVars = require("./globalVars.js");
var { aspect } = globalVars.Modules;
module.exports.getHumanData = function(){
return "MR. " aspect.getAspect();
};
Please don't answer me to put everything in the same file, because my code is way more complicated to report so here I'm posting this very simple simulation.
I know that objects are assigned by reference and so if all modules get the variables from "globalVars" they works kinda like "global".
The problem is when in globalFunctions.js I load
var { aspect } = globalVars.Modules;
Since in server.js the module aspect.js is not loaded yet, it will be an empty object. But I'm expecting that
var { aspect } = globalVars.Modules;
is getting the reference of globalVars and not a copy, so when server.js finishes loading all the modules, the variabile aspect inside globalVars.Modules will point to the correct object and so it would find the function I need!
Infact the console.log inside server.js is executed after all modules have been loaded for this exact reason.
Does anyone know what is the reason of this problem and how could I solve it? Thank to everyone who will help!
CodePudding user response:
As you've noticed, var { aspect } = globalVars.Modules;
copies the current value of globalVars.Modules.aspect
to the local variable aspect
. It's just alternative syntax for var aspect = globalVars.Modules.aspect
.
If you later change the value of globalVars.Modules.aspect
to a new object (as opposed to mutating the object that is already there) then the local variable doesn't update.
If you want the most recent value then you need to continue accessing globalVars.Modules.aspect
whenever you need it.
CodePudding user response:
What's happening
It's an issue of what it means to do const { aspect } = globalVars.Modules;
(which is the same as const aspect = globalVars.Modules.aspect;
). That is, it's a matter of assignment semantics.
Let's look at a simpler example, then we can see hot it applies to what you're doing. Assume you have:
let a = {/*original object*/};
When you do
b = a;
the value in a
is copied into b
. That value is an object reference, so they now both point to the same object, but there is no ongoing link between a
and b
. If you then do
a = {/*new object*/};
that has no effect whatsoever on b
, because there's no ongoing link between a
(the variable) and b
(the variable). b
still refers to the original object, not the new one.
The same is true of any assignable item, such as an object property or parameter. That's what's happening with globalVars.Modules.aspect
. globalFunctions
is grabbing the value (simple assignment semantics, though using destructuring), then server.js
is replacing that value with a new one.
Here's how that's happening in your code:
// server.js
var globalVars = (function() {
// globalVars.js
return { // this is the `exports` object
Modules: {
aspect: {}, // *** That's the `a = {/*original object*/}`
}
};
})();
// back in server.js
globalVars.Modules.globalFunctions = (function() {
// globalFunctions.js
const { aspect } = globalVars.Modules; // **** That's the `b = a`
return { // this is the `exports` object
getHumanData: function(){
return "MR. " aspect.getAspect();
}
};
})();
// back in server.js
globalVars.Modules.aspect = (function() { // *** that's the `a = {/*new object*/}
return { // this is the `exports` object
getAspect: function(){
return "human";
}
};
})();
// back in server.js
globalVars.Modules.globalFunctions.getHumanData(); // Fails because the object it's using
// is the old one, not the new one
How to fix it
globalFunctions.js
relies on aspect.js
, so have it rely on it directly:
// In `globalFunctions.js`
const aspect = require("./aspect.js");
module.exports.getHumanData = function(){
return "MR. " aspect.getAspect();
};
Assuming there are no cycles, that'll work.
At a larger level: There may be no reason to have globalVars.Modules
at all. A module is only loaded once (normally), so rather than globalVars.Modules
, just have each module directly rely on the module it needs, rather than funnelling it all through a central object. Node.js' module cache is already the central object.
If you don't want globalFunctions.js
to rely on aspect.js
directly (why not?), then don't copy the aspect
property from Modules
, use it as of when you need it:
// In `globalFunctions.js`
const {Modules} = require("./globalVars.js");
module.exports.getHumanData = function(){
return "MR. " Modules.aspect.getAspect();
};
That'll work assuming nothing reassigns Modules
(which nothing seems to in the code you've shown). But again, it makes more sense to rely on aspect.js
directly if you can.
It's kind of fun to note that this is one of the reasons that modern ESM modules don't use simple assignment semantics like CommonJS ones do. ESM wouldn't help your specific thing, because you're using your own globalVars.Modules
object instead of using the module objects, but it solves a problem that people often had with CommonJS modules which, like your problem, was caused by expecting b
(an imported value) to be affected when reassigning a
(the exported value). The issue people would have with CommonJS happened mostly when there were cycles between two modules (circular dependencies, directly or indirectly). ESM solves this by making the imported binding (b
in my example) a live binding to the exported binding (a
in my example). This is the only place JavaScript has what you could argue is a form of pass-by-reference (references to variables).