Circular Dependencies in JavaScript

Shukant Pal
2 min readMar 14, 2019

--

Trust me, you need to remove those circular dependencies in your project!

Photo by Lysander Yuen on Unsplash

The JavaScript world is emphasizing modular code, however, support for circular dependencies is limited. I propose a once-and-for-all solution to that problem here — by using what I call API binding.

Case

While developing a multi-precision JavaScript library, I had accumulated ~1,000 lines of code in a BigInteger file. That’s not an acceptable amount of code in one file, so I decided to RIP it apart.

husky-multiprecision/
src/
BigInteger.js
IntegerMultiplication.js // provides a static multiply()
IntegerDivision.js // provides a static divide()
index.js

The structure of my project included these files. IntegerDivision and IntegerMultiplication obviously depend on BigInteger , while BigInteger depends on the other two to expose API functions BigInteger.multiply() and BigInteger.divide() . Now, using circular dependencies here is not a solution — that is because this is a library, not an application, which needs to satisfy all types of module systems, including those that don’t like circular dependencies.

Binding an extension API on the BigInteger class will allow external modules to use BigInteger.multiply() and BigInteger.divide() while internal files will use IntegerMultiplication and IntegerDivision APIs.

Let’s use Binding

Since external libraries and applications using my multi-precision library will “require” the index.js file, and won’t touch any internal file, I can hide the circular dependency inside the src directory by creating a “Extension API” and binding it to the BigInteger class.

// husky-multiprecision/BigInteger.extension.js// After binding BigIntegerExtension_* functions to
// the BigInteger class (in index.js), we can call
// multiply and divide on a BigInteger object.
const BigInteger = require('./BigInteger');
const IntegerMultiplication = require('./IntegerMultiplication');
const IntegerDivision = require('./IntegerDivision);
function BigIntegerExtension_multiply(other) {
return IntegerMultiplication.multiply(this, other);
}
function BigIntegerExtension_divide(other) {
return IntegerDivision.divide(this, other);
}
module.exports = {
multiply: BigIntegerExtension_multiply,
divide: BigIntegerExtension_divide
);

Now, we need to “bind” the API in BigInteger.extension.js :

// husky-multiprecision/index.js/*
* Code in the global scope here is invoked only once,
* no matter how many times this file is imported, hence,
* don't worry about the binding occurring more than
* once.
*
* Don't believe me, try adding console.log("Binder"); anywhere
* here and import it twice. "Binder" will be shown only once.
*/
const BigInteger = require('./src/BigInteger');
const BigIntegerExtAPI = require('./BigInteger.extension.js');
BigInteger.prototype.multiply = BigIntegerExtAPI.multiply;
BigInteger.prototype.divide = BigIntegerExtAPI.divide;
module.exports = {
BigInteger: BigInteger
};

Hooray!!! Now you can use the binding pattern to create fine, modular code without having to deal with circular dependencies.

--

--