znat
znat

Reputation: 13494

Why my exported function is not a function?

Consider the following example:

// bar.js
const foo = require('./foo');    

module.exports = function(args){
    let f = foo(args)
}
// foo is not a function

Then:

// bar.js
module.exports = function(args){
    let f = require('./foo')(args)
}
// behaves as expected

foo.js looks like:

const bar = require('./bar');  
module.exports = function(args){ //same args as bar.js
    const foo = {};
    foo.f1 = function(arg){
        console.log("Hi")
    }

    return foo
};

Upvotes: 1

Views: 4165

Answers (1)

Tobias
Tobias

Reputation: 7771

You are dealing with the problems of cyclic dependencies. I guess foo.js looks somehow like this:

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

module.exports = function(args) {
  console.log(args);
};

Let's follow the steps the VM performs to execute the code (assuming you load foo.js first):

  1. Load foo.js. Initialize exports with A = {}.
  2. Load bar.js. Initialize exports with B = {}. Now within bar.js:
    1. bar.js requires the exports of foo.js (which are still A = {}), so set foo in bar.js to A = {} (this is the problem!).
    2. Replace the exports of bar.js with the function as declared.
  3. Replace the exports of foo.js with the function as declared.

As you can see, there is no way for the VM to keep track of the exports of foo.js within bar.js if you use cyclic dependencies.

In your second example, you resolve the exports of foo.js later, when the function is called. At that point, the exports of foo.js have already been replaced with the function, and it works.


Short version: This is what happens.

  1. The VM loads foo.js and initializes the exports of foo.js with object A.
  2. The VM loads bar.js and initializes the exports of bar.js with object B.
  3. The VM sets the variable foo in bar.js to the exports of foo.js, therefore foo = A.
  4. The VM replaces the exports of bar.js with the function C
  5. The VM sets the variable bar in foo.js to the exports of bar.js, therefore bar = C.
  6. The VM replaces the exports of foo.js with the function D.

As you can see again, the variable foo in bar.js is still A, even though it should be D.


You will usually want to avoid using cyclic dependencies. If it is really necessary, then do not replace module.exports, but rather attach properties to the object:

module.exports.doBarAction = function(args){
    let f = foo.doFooAction(args)
};

This solves the problem as the object is still the same at runtime.
However, in most situations it is better to get rid of cyclic dependencies, at least as long as we are talking about CommonJS modules.

Upvotes: 5

Related Questions