Circular References in Node.js
In Node.js, when a circular reference is detected, the require that triggered that reference returns an empty object instead of the normal full object. For example, here is one module in a file called circularReferenceB.js:
const circularReferenceA = require('./circularReferenceA'); module.exports = { printProperty1: () => { console.log(circularReferenceA.property1); } };
And here is one in circularReferenceA.js which is run directly with node circularReferenceA.js:
const circularReferenceB = require('./circularReferenceB'); circularReferenceB.printProperty1(); // undefined module.exports = { property1: 'value1' };
The circularReferenceB.printProperty1 returns undefined because circularReferenceA has not been initialized yet. A log in circularReferenceB.js makes this clear:
console.log('circularReferenceA require:', circularReferenceA); // circularReferenceA require: {}
Keep in mind the required relationships don't need to be directly circular. For example, A->B->C->A will result in C getting an empty A module by default. The key is that JavaScript is executed dynamically, so each require is executed as it is found. So circularReferenceB.js gets the non-initialized circularReferenceA.js which is empty by default.
There are a few general ways to fix this:
Duplicate needed code: Simple and gauranteed to work, but this can create maintainence nightmares.
Set any module.exports at the top: If this is set before any code is run, at least that code should be getting the latest version of the module. The issue is if the module.exports requires the code in the module to be run first to generate some of its content.
Use dependency injection The part of the needed module can simply be passed in. There is some up extra logic needed for this across multiple modules, but it should handle every situation without code duplication.
Below is an example of 3 given the above files. Firstly circularReferenceA.js:
const circularReferenceB = require('./circularReferenceB'); const property1 = 'value1'; circularReferenceB.printProperty1(property1); // value1 module.exports = { property1: property1 };
And circularReferenceB.js:
const circularReferenceA = require('./circularReferenceA'); module.exports = { printProperty1: (property1) => { console.log(property1); } };
Despite all of the above, it is best to evaluate why a circular dependency came up because it almost always indicates a flaw in software architecture. Though when the timelines are short or there is no way around the relationship, then keep in mind the above.
Github Location: https://github.com/Jacob-Friesen/obscurejs/blob/master/2017/circularReferenceA.js














