Home > Software design >  Node.JS Is it possible to prevent node from stopping execution of circular dependencies?
Node.JS Is it possible to prevent node from stopping execution of circular dependencies?

Time:11-17

I'm writing a program with several threads and a lot of requires. I need circular dependencies otherwise there will be too many files.

I have a function that can prevent it from looping, however node complains about it before it can even run. I've tried looking for a way, but its hard to find a "bypass" instead of moving a lot of stuff around.

Is it possible to disable the protections and let it run anyway?

Here's my code;

constants.js

// This should prevent any circular dependency but node shuts it down too eagerly.
function _getCallerFile() {
    // Returns the file that originally called.
    
    var err = new Error();
    Error.prepareStackTrace = (_, stack) => stack;
    Error.prepareStackTrace = undefined;
    return err.stack.toString();
}

var x = _getCallerFile();
console.log(x);
console.log(x.includes(".js"))
console.log(x.includes("threading.js"))

//if (!x.includes("input.js")) var input = require('./constants/input.js');
if (!x.includes("threading.js")) var threading = require('./constants/threading.js');

module.exports ={ 
    threading: threading
    //input: input
};

threading.js

var x = require('../constants.js');

module.exports.init = async function (whoami) {
    x.sendMsg("debug", whoami, `Starting!`);
}

index.js

var x = require('./constants.js');

async function main() { 
  x.threading.init(whoami);
}
main();

CodePudding user response:

Node.JS Is it possible to prevent node from stopping execution of circular dependencies?

Nodejs cannot run with many types of circular dependencies. There are some ways you can live with certain things if you are really, really careful in how you write your code, what order you define things in and what order you reference things in. See the description here for details, but in general, it's much safer to just remove the circular dependency in its entirety rather than try to live with it or code around it.

Imagine the maintenance liability of your code if you very carefully create a circular dependency that actually works because of the careful timing of when you declare and access things and then an innocent looking modification to the code suddenly breaks things. It is generally safer to not have circular dependencies and a little refactoring into shared files can usually solve it easily and safely.

The usual solution here is to rework the layout of your code to avoid creating the circular dependency in the first place. It is always possible to break some common code into a shared file and avoid the circular dependency and you should be able to do this without creating a zillion files.

In some cases, it's simpler to combine two files into one file where you can have circular code dependencies between say two classes within the same file. If you show the real code with the real problem, we could advise on good options for avoiding the circular dependency.


In your specific example, you could work around it by delaying one of your require() statements. For example, if you change part of threading.js from this:

var x = require('../constants.js');

module.exports.init = async function (whoami) {
    x.sendMsg("debug", whoami, `Starting!`);
}

to this:

module.exports.init = async function (whoami) {
    var x = require('../constants.js');
    x.sendMsg("debug", whoami, `Starting!`);
}

Then, you no longer run into the circular dependency because constants.js is not loaded by threading.js until AFTER threading.js is already done being loaded. Or, another way of looking at it, is that threading.js loads without requiring constants.js, thus avoiding the circularity during load. Note, this won't translate as well to ESM modules which requires that dynamic (non-static) imports be asynchronous and use the function version of import() that returns a promise.

This will load constants.js upon the first call to .init() which isn't ideal, but once it is loaded the first time, it will come from the module cache every time after that.

I'd still personally suggest refactoring to avoid this, but there are some tricks with load timing that can avoid the circularity.

CodePudding user response:

Your maybe over complicating things here.

Looking at your code using the _getCallerFile hack to try and auto require looks like a problem waiting to happen. eg. What if you legitimately had two copies of threading.js.

Below is your code adjusted to work the way you want without having to implement any funky stuff.

constants.js

module.exports ={ 
   // threading: threading <- filled during bootstrap stage
};

threading.js

var x = require('../constants.js');

async function init(whoami) {
    x.sendMsg("debug", whoami, `Starting!`);
}

//bootstrap
x.threading = init;

module.exports = init;

index.js

require('./threading.js'); //bootstrap
var x = require('./constants.js');

async function main() { 
  x.threading.init(whoami);
}
main();

What's even better, doing it this way will work if you ever decided to go ES module loader, or you wanted start using TypeScript.

  • Related