How to deal with cyclic dependencies in Node.js

后端 未结 13 1962
别那么骄傲
别那么骄傲 2020-11-22 04:36

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 following b

相关标签:
13条回答
  • 2020-11-22 04:55

    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.

    0 讨论(0)
  • 2020-11-22 04:57

    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,
    });
    
    0 讨论(0)
  • 2020-11-22 04:58

    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.

    0 讨论(0)
  • 2020-11-22 05:00

    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
    
    0 讨论(0)
  • 2020-11-22 05:04

    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
    
    0 讨论(0)
  • 2020-11-22 05:06

    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, which you can add other properties latter on, but when you set the entire module.exports, you actually create a new object which the requiring module has no way to access.

    0 讨论(0)
提交回复
热议问题