Skip to content

Instantly share code, notes, and snippets.

@pmuellr
Created December 15, 2013 16:43
Show Gist options
  • Save pmuellr/7975152 to your computer and use it in GitHub Desktop.
Save pmuellr/7975152 to your computer and use it in GitHub Desktop.
// module a
b = require("./b")
module.exports = function() {
console.log("I'm in module a")
}
b()
// module b
a = require("./a")
module.exports = function() {
a()
}

In node.js, you need to be careful when you export functions out of a module using the module.exports = style of export.

Try re-creating the two files a.js and b.js on your computer now, and then run node a. You should see an error like the following:

b.js:4
    a()
    ^
TypeError: Property 'a' of object #<Object> is not a function
    at module.exports (b.js:4:5)
    at Object.<anonymous> (a.js:7:1)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)
    at startup (node.js:119:16)
    at node.js:901:3

How does this happen? Let's walk the steps.

Node loads module a because you specified loading it on the command-line. Internally, node creates a new empty object {} for the exports for the object; this object will actually be the value of the exports variable when module a is run.

The first thing module a does is load module b. So, let's move to module b.

Again, node will create a new empty object {} for the exports for the module. The first thing module b does is to require module a. Node is actually still in the process of loading module a - it hasn't finished - but it did already create the exports for module a, and so it returns them. At this point, the variable a in module b will be set to the new empty object {}. When a is invoked in the exported function below this, the error will result.

You can see in module a that it's supposed to be exporting a function. The problem is that this isn't done before module b does a require() on a, so b will end up getting access to the empty module export initially set for a.

@pmuellr
Copy link
Author

pmuellr commented Dec 16, 2013

Ya, I've only seen this "problem" in mutually require()'d modules, and it's fixed by assigning your module.exports before doing other require()'s (or the require()'s that cause the problem anyway).

In practice, I've seen this is in some of my packages where I try doing things like "every module is a class!", etc. These things typically don't survive very long. :-) And it's can be painful to deal with superclasses this way as well, since then you need to require() the superclass early (for CoffeeScript).

@pmuellr
Copy link
Author

pmuellr commented Dec 16, 2013

I'm trying to remember, in some of my old, early CommonJS experiments, if I had code to handle this. I thinking the best I could do is provide a warning, in cases where I recognized mutually recursive require()'s. Not helpful, because you don't want to see this "warning" all the time, so you would need to have folks opt-in for it, and then no one is going to use it.

I guess it's moot if no one ever sees this in practice. I will note that when I started seeing this myself, I was often confused as to what was going on. So even if it's a rare problem, it's a difficult to diagnose one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment