How to deal with cyclic dependencies in Node.js
Asked Answered
P

16

233

I've been working with nodejs lately and still getting to grips with the module system, so apologies if this is an obvious question. I want code roughly like the below:

a.js (the main file run with node)

var ClassB = require("./b");

var ClassA = function() {
    this.thing = new ClassB();
    this.property = 5;
}

var a = new ClassA();

module.exports = a;

b.js

var a = require("./a");

var ClassB = function() {
}

ClassB.prototype.doSomethingLater() {
    util.log(a.property);
}

module.exports = ClassB;

My problem seems to be that I can't access the instance of ClassA from within an instance of ClassB.

Is there any correct / better way to structure modules to achieve what I want? Is there a better way to share variables across modules?

Pulcheria answered 3/6, 2012 at 9:42 Comment(2)
I suggest you look in to command query separation, observable pattern and then what the CS guys call managers - which is basically a wrapper for the observable pattern.Backstroke
nodejs.org/api/modules.html#modules_cyclesBluepencil
S
128

While node.js does allow circular require dependencies, as you've found it can be pretty messy and you're probably better off restructuring your code to not need it. Maybe create a third class that uses the other two to accomplish what you need.

Sunset answered 3/6, 2012 at 18:49 Comment(10)
+1 This is the right answer. Circular dependencies are code smell. If A and B are always used together they are effectively a single module, so merge them. Or find a way of breaking the dependency; maybe its a composite pattern.Kirsch
Not always. in database models, for example, if I have model A and B, in model A I may want to reference model B (e.g. to join operations), and vice-versa. Therefore, export several A and B properties (the ones that does not depend on other modules) before use the "require" function may be a better answer.Cirillo
I also don't see circular dependencies as code smell. I'm developing a system where there are a few cases where it is needed. For example, modeling teams and users, where users can belong to many teams. So, it's not that something is wrong with my modeling. Obviously, I could refactor my code to avoid the circular dependency between the two entities, but that would not be the most pure form of the domain model, so I will not do that.Stenophagous
Then should I inject the dependency when needed, is that what you mean? Using a third to control the interaction between the two dependencies with the cyclic problem?Kildare
This is not messy.. someone may want to brake a file to avoid a book of code i a single file. As node suggests you should add an exports = {} at the top of your code and then exports = yourData at the end of your code. With this practice you will avoid almost all errors from circular dependencies.Kerrison
Yet another legit reason two modules might depend on each other: they are written by different authors and should remain separate files because they have different roles in the system.Afforest
log.js uses conf.js to determine logfile location. conf.js uses log.js to report errors parsing the conf files. Does it make sense to merge those?Neritic
Smell doesn't mean bad. While there are cases where circular dependencies might be right, it's definitely a warning flag that something might be wrong or could easily be if you are not cautious. That's a smell.Nedi
Seems like people forget that domains can have optional dependencies. If your code depends on something that depends on you, make said dependency optional. Linux world solved this issue years ago.Cartwell
What happens if a require("b") and b require("a") simultanuous?Saberhagen
K
206

Try to set properties on module.exports, instead of replacing it completely. E.g., module.exports.instance = new ClassA() in a.js, module.exports.ClassB = ClassB in b.js. When you make circular module dependencies, the requiring module will get a reference to an incomplete module.exports from the required module, to which you can later add other properties, but when you set the entire module.exports, you actually create a new object which the requiring module has no way to access.

Ken answered 3/6, 2012 at 18:54 Comment(3)
This might be all true, but I would say still avoid circular dependencies. Making special arrangements to deal with modules that have incompletely loaded sounds like it will create a future problem you don't want to have. This answer prescribes a solution to how to deal with incompletely loaded modules...I don't think that's a good idea.Dinorahdinosaur
How would you put a class constructor in module.exports without fully replacing it, to allow other classes to 'construct' an instance of the class?Nyaya
I don't think you can. Modules that have imported your module already will not be able to see that changeKen
S
128

While node.js does allow circular require dependencies, as you've found it can be pretty messy and you're probably better off restructuring your code to not need it. Maybe create a third class that uses the other two to accomplish what you need.

Sunset answered 3/6, 2012 at 18:49 Comment(10)
+1 This is the right answer. Circular dependencies are code smell. If A and B are always used together they are effectively a single module, so merge them. Or find a way of breaking the dependency; maybe its a composite pattern.Kirsch
Not always. in database models, for example, if I have model A and B, in model A I may want to reference model B (e.g. to join operations), and vice-versa. Therefore, export several A and B properties (the ones that does not depend on other modules) before use the "require" function may be a better answer.Cirillo
I also don't see circular dependencies as code smell. I'm developing a system where there are a few cases where it is needed. For example, modeling teams and users, where users can belong to many teams. So, it's not that something is wrong with my modeling. Obviously, I could refactor my code to avoid the circular dependency between the two entities, but that would not be the most pure form of the domain model, so I will not do that.Stenophagous
Then should I inject the dependency when needed, is that what you mean? Using a third to control the interaction between the two dependencies with the cyclic problem?Kildare
This is not messy.. someone may want to brake a file to avoid a book of code i a single file. As node suggests you should add an exports = {} at the top of your code and then exports = yourData at the end of your code. With this practice you will avoid almost all errors from circular dependencies.Kerrison
Yet another legit reason two modules might depend on each other: they are written by different authors and should remain separate files because they have different roles in the system.Afforest
log.js uses conf.js to determine logfile location. conf.js uses log.js to report errors parsing the conf files. Does it make sense to merge those?Neritic
Smell doesn't mean bad. While there are cases where circular dependencies might be right, it's definitely a warning flag that something might be wrong or could easily be if you are not cautious. That's a smell.Nedi
Seems like people forget that domains can have optional dependencies. If your code depends on something that depends on you, make said dependency optional. Linux world solved this issue years ago.Cartwell
What happens if a require("b") and b require("a") simultanuous?Saberhagen
C
61

[EDIT] it's not 2015 and most libraries (i.e. express) have made updates with better patterns so circular dependencies are no longer necessary. I recommend simply not using them.


I know I'm digging up an old answer here... The issue here is that module.exports is defined after you require ClassB. (which JohnnyHK's link shows) Circular dependencies work great in Node, they're just defined synchronously. When used properly, they actually solve a lot of common node issues (like accessing express.js app from other files)

Just make sure your necessary exports are defined before you require a file with a circular dependency.

This will break:

var ClassA = function(){};
var ClassB = require('classB'); //will require ClassA, which has no exports yet

module.exports = ClassA;

This will work:

var ClassA = module.exports = function(){};
var ClassB = require('classB');

I use this pattern all the time for accessing the express.js app in other files:

var express = require('express');
var app = module.exports = express();
// load in other dependencies, which can now require this file and use app
Carve answered 21/1, 2014 at 16:51 Comment(4)
thank you for sharing the pattern and then further sharing how you commonly use this pattern when exporting out app = express()Arsenite
You recommend not using them, but i have an abstract class which needs to be extended on another file with a class, and also need to include that class in abstract class defined file to execute it as the main, i cant do anything but use circular for this i guess, thank youTetravalent
Generally, libraries should not affect the node runtime in a way that modifies how node compilation unless the library is literally a compiler.Plio
How would that work with ESM? You're not allowed to have any code before imports.Wolfie
W
47

Sometimes it is really artificial to introduce a third class (as JohnnyHK advises), so in addition to Ianzz: If you do want to replace the module.exports, for example if you're creating a class (like the b.js file in the above example), this is possible as well, just make sure that in the file that is starting the circular require, the 'module.exports = ...' statement happens before the require statement.

a.js (the main file run with node)

var ClassB = require("./b");

var ClassA = function() {
    this.thing = new ClassB();
    this.property = 5;
}

var a = new ClassA();

module.exports = a;

b.js

var ClassB = function() {
}

ClassB.prototype.doSomethingLater() {
    util.log(a.property);
}

module.exports = ClassB;

var a = require("./a"); // <------ this is the only necessary change
Welcher answered 31/12, 2012 at 6:26 Comment(4)
thanks coen, I had never realized that module.exports had an effect on circular dependencies.Heigl
this is especially useful with Mongoose (MongoDB) models; helps me to fix a problem when the BlogPost model has an array with references to comments, and each Comment model has reference to the BlogPost.Fusible
this is the right answer for me for mongoose middleware implementations for related schemas. creating a third class as suggested by the accepted answer didn't really solve the problem as it still imports the classes implicitly.Ovipositor
did not work for me, had to refactor to remove the cyclic dependencyInterdependent
H
16

The solution is to 'forward declare' your exports object before requiring any other controller. So if you structure all your modules like this and you won't run into any issues like that:

// Module exports forward declaration:
module.exports = {

};

// Controllers:
var other_module = require('./other_module');

// Functions:
var foo = function () {

};

// Module exports injects:
module.exports.foo = foo;
Hirokohiroshi answered 18/8, 2014 at 0:35 Comment(2)
Actually, this led me to simply use exports.foo = function() {...} instead. Definitely did the trick. Thanks!Curate
I'm not sure what you're proposing here. module.exports is already a plain Object by default, so your "forward declaration" line is redundant.Matriarchate
M
12

NOTE THAT THE TOTALLY CORRECT ANSWER IS GIVEN BY CAMILO:

// DO NOT DO THIS
// module.exports = {
//     tell, devTell, devTellPermanentLog,
//     updateAdmin
// }

// DO THIS:
exports.tell = tell
exports.devTell = devTell
exports.devTellPermanentLog = devTellPermanentLog
exports.updateAdmin = updateAdmin

It's incredible that for the literally 100s of thousands of words written about circulars in node.js, that is the whole solution.

That's all there is to it.

Thank you, Camilo.


Here is a quick workaround that is worth knowing, if you don't have time to refactor:

An extremely simple "quick solution" if you just need something to run immediately is:

Usually you'd have the require at the top of the file ...

var script = require('./script')
function stuff() {
      script.farfunction()
}

instead, just require it "in the function"

function stuff() {
      var _script = require('./script')
      _script.farfunction()
}
Murdocca answered 17/1, 2022 at 12:35 Comment(1)
this is solving problem. you all right. but how correct way. I dont knowDeprecative
V
10

What about lazy requiring only when you need to? So your b.js looks as follows

var ClassB = function() {
}
ClassB.prototype.doSomethingLater() {
    var a = require("./a");    //a.js has finished by now
    util.log(a.property);
}
module.exports = ClassB;

Of course it is good practice to put all require statements on top of the file. But there are occasions, where I forgive myself for picking something out of an otherwise unrelated module. Call it a hack, but sometimes this is better than introducing a further dependency, or adding an extra module or adding new structures (EventEmitter, etc)

Verrucose answered 17/4, 2017 at 13:36 Comment(2)
And sometimes it's critical when dealing with a tree data structure with child objects maintaining references to a parent. Thanks for the tip.Sib
I wouldn't call this a hack. This is completely valid, and just because people think you should put every require at the top of the page, it's absolutely not a _require_ment.Fotinas
B
10

You can solve this easily: just export your data before you require anything else in modules where you use module.exports:

classA.js

class ClassA {

    constructor(){
        ClassB.someMethod();
        ClassB.anotherMethod();
    };

    static someMethod () {
        console.log( 'Class A Doing someMethod' );
    };

    static anotherMethod () {
        console.log( 'Class A Doing anotherMethod' );
    };

};

module.exports = ClassA;
var ClassB = require( "./classB.js" );

let classX = new ClassA();

classB.js

class ClassB {

    constructor(){
        ClassA.someMethod();
        ClassA.anotherMethod();
    };

    static someMethod () {
        console.log( 'Class B Doing someMethod' );
    };

    static anotherMethod () {
        console.log( 'Class A Doing anotherMethod' );
    };

};

module.exports = ClassB;
var ClassA = require( "./classA.js" );

let classX = new ClassB();
Bast answered 30/4, 2018 at 9:10 Comment(0)
I
8

A solution which require minimal change is extending module.exports instead of overriding it.

a.js - app entry point and module which use method do from b.js*

_ = require('underscore'); //underscore provides extend() for shallow extend
b = require('./b'); //module `a` uses module `b`
_.extend(module.exports, {
    do: function () {
        console.log('doing a');
    }
});
b.do();//call `b.do()` which in turn will circularly call `a.do()`

b.js - module which use method do from a.js

_ = require('underscore');
a = require('./a');

_.extend(module.exports, {
    do: function(){
        console.log('doing b');
        a.do();//Call `b.do()` from `a.do()` when `a` just initalized 
    }
})

It will work and produce:

doing b
doing a

While this code will not work:

a.js

b = require('./b');
module.exports = {
    do: function () {
        console.log('doing a');
    }
};
b.do();

b.js

a = require('./a');
module.exports = {
    do: function () {
        console.log('doing b');
    }
};
a.do();

Output:

node a.js
b.js:7
a.do();
    ^    
TypeError: a.do is not a function
Indoeuropean answered 26/6, 2014 at 10:31 Comment(1)
If you don't have underscore, then ES6's Object.assign() can do the same work that _.extend() is doing in this answer.Extol
E
7

The important thing is not to re-assign the module.exports object that you have been given, because that object may have already been given to other modules in the cycle! Just assign properties inside module.exports and other modules will see them appear.

So a simple solution is:

module.exports.firstMember = ___;
module.exports.secondMember = ___;

The only real downside is the need to repeat module.exports. many times.


Similar to lanzz and setec's answers, I have been using the following pattern, which feels more declarative:

module.exports = Object.assign(module.exports, {
    firstMember: ___,
    secondMember: ___,
});

The Object.assign() copies the members into the exports object that has already been given to other modules.

The = assignment is logically redundant, since it is just setting module.exports to itself, but I am using it because it helps my IDE (WebStorm) to recognise that firstMember is a property of this module, so "Go To -> Declaration" (Cmd-B) and other tooling will work from other files.

This pattern is not very pretty, so I only use it when a cyclic dependency issue needs to be resolved.

It is fairly well suited to the reveal pattern, because you can easily add and remove exports from the object, especially when using ES6's property shorthand.

Object.assign(module.exports, {
    firstMember,
    //secondMember,
});
Extol answered 3/3, 2017 at 4:6 Comment(1)
"not to re-assign the module.exports object that you have been given, because that object may have already been given to other modules" might be the reason, tksSquiffy
M
7

TL;DR

Just use exports.someMember = someMember instead of module.exports = { // new object }.

Extended Answer

After reading lanzz's response I could finally figure it out what is happening here, so I'll give my two cents on the subject, extending his answer.

Let's see this example:

a.js

console.log("a starting");

console.log("a requires b");
const b = require("./b");
console.log("a gets b =", b);

function functionA() {
  console.log("function a");
}

console.log("a done");
exports.functionA = functionA;

b.js

console.log("b starting");

console.log("b requires a");
const a = require("./a");
console.log("b gets a =", a);

function functionB() {
  console.log("On b, a =", a)
}

console.log("b done");
exports.functionB = functionB;

main.js

const a = require("./a");
const b = require("./b");

b.functionB()

Output

a starting
a requires b
b starting
b requires a
b gets a = {}
b done
a gets b = { functionB: [Function: functionB] }
a done
On b, a = { functionA: [Function: functionA] }

Here we can see that at first b receives an empty object as a, and then once a is fully loaded, that reference is updated through exports.functionA = functionA. If you instead replace the entire module with another object, through module.exports, then b will lose the reference from a, since it will point out to the same empty object from the beginning, instead of pointing to the new one.

So if you export a like this: module.exports = { functionA: functionA }, then the output will be:

a starting
a requires b
b starting
b requires a
b gets a = {}
b done
a gets b = { functionB: [Function: functionB] }
a done
On b, a = {} // same empty object
Multiplicity answered 1/4, 2021 at 20:15 Comment(0)
P
6

An other method I've seen people do is exporting at the first line and saving it as a local variable like this:

let self = module.exports = {};

const a = require('./a');

// Exporting the necessary functions
self.func = function() { ... }

I tend to use this method, do you know about any downsides of it?

Pastry answered 20/12, 2017 at 17:58 Comment(1)
you can rather do module.exports.func1 = , module.exports.func2 = Tantalizing
V
4

Actually I ended up requiring my dependency with

 var a = null;
 process.nextTick(()=>a=require("./a")); //Circular reference!

not pretty, but it works. It is more understandable and honest than changing b.js (for example only augmenting modules.export), which otherwise is perfect as is.

Verrucose answered 17/4, 2017 at 14:1 Comment(2)
Of all the solutions on this page, this is the only one that solved my problem. I tried each in turn.Constanceconstancia
This is the only solution that actually works for me. I've been trying to find a solution for this for days. Thank you so much!Prerogative
B
3

Here is a quick workaround that I've found use full.

On file 'a.js'

let B;
class A{
  constructor(){
    process.nextTick(()=>{
      B = require('./b')
    })
  } 
}
module.exports = new A();

On the file 'b.js' write the following

let A;
class B{
  constructor(){
    process.nextTick(()=>{
      A = require('./a')
    })
  } 
}
module.exports = new B();

This way on the next iteration of the event loop classes will be defined correctly and those require statements will work as expected.

Baldwin answered 24/5, 2019 at 3:0 Comment(5)
this is not a solution. this is just a scape plan. but it is funHaarlem
@mohammadjawadBarati - "this is not a solution". It is an answer to a problem, how is that not a solution?Vidavidal
@Vidavidal because you just require b in the nextTick & this is not the right way. s/he must change his/her coding style to another way. if your code requires something earlier than it should require it, it's wrong. you should manage it to don't face this problem or anything like thisHaarlem
@mohammadjawadBarati Sounds like you're a bit close minded, your way or the highway kind of mantra going on here. Its a solution, it works, if it works and provides the expected result, its not wrong, thats what a solution is. There can be multiple solutions to the same problem, you don't have to like it. Everyone has their own style.Vidavidal
This eliminates the ability to have static methods on the class.Wadmal
G
0

One way to avoid it is to don't require one file in other just pass it as an argument to a function what ever you need in an another file. By this way circular dependency will never arise.

Goldwin answered 25/4, 2020 at 14:42 Comment(2)
I would expect it to be fairly common for one module to need to reference another.Heterogamy
Yes, we take dependencies and inject them in the respective functions, dependency injection.Bonitabonito
C
-5

If you just can't eliminate circular dependencies (e.g useraccount <---> userlogin), there's one more option...

Its as simple as using setTimeout()

//useraccount.js

let UserLogin = {};

setTimeout(()=>UserLogin=require('./userlogin.js'), 10);

class UserAccount{
 
getLogin(){
return new UserLogin(this.email);

}

}



//userlogin.js

let UserAccount ={};

setTimeout(()=>UserAccount=require('./useraccount.js'), 15);


class UserLogin{

getUser(){

return new User(this.token);

}

}
Countermand answered 16/12, 2020 at 0:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.