Search code examples
javascriptnode.jsruntime-error

why do interdepedent functions across modules evaluate to undefined at runtime in node javascript


I have three javascript files

moduleA.js, moduleB.js, and index.js

moduleA.js

const {b1} = require('./moduleB');
const a1 = ()=>{
    console.log('a1 called')
};
const a2 = ()=>{
    console.log('a2 called');
    b1()
};

module.exports = {a1, a2};

moduleB.js

const {a1} = require('./moduleA');

const b1 = () => {
    console.log('b1 called');
    a1();
};
const b2 = () => {
    console.log('b2 called');
};

module.exports = {b1, b2};

index.js

const {a2} = require('./moduleA');
a2();

How come invoking a2() throws an error at runtime because a2 is undefined in node? I've not seen this type of error in another language such as Java.

Is there a solution to this apart from putting a2 in another file such as moduleC.js and call moduleC.a2()?

example code on codingrooms

The current output is:

TypeError: a1 is not a function
    at b1 (... /moduleB.js:5:5)
    at a2 (... /moduleA.js:7:5)

Expected output is

// a2 called
// b1 called
// a1 called

Additional information

Based on the accepted answer provided by Felix and my understanding of the Modules chapter in Matt Frisbie's Professional JavaScript for Web Developers. I drew this graph to assist my understanding of the module loading process in CommonJS. I hope whoever stumbles upon this question in the future will find it helpful as much as I did.

module loading with circular dependency


Solution

  • As mentioned in the comments, the error is that a1 is undefined, not a2. The reason for that is that you have a circular dependency between module A and module B.

    At the time module B imports a1 from module A, module A's exports object is still empty, i.e. a1 doesn't exist yet.

    To fix this you need to do two things:

    • Delay ccessing a1 until it's needed (which is after all modules are evaluated).
    • Do not overwrite the initial exports object but update it instead.
    const a = require('./moduleA');
    //   ^^^ 
    
    const b1 = () => {
        console.log('b1 called');
        a.a1();
    //  ^^
    };
    const b2 = () => {
        console.log('b2 called');
    };
    
    module.exports = {b1, b2};
    
    const {b1} = require('./moduleB');
    exports.a1 = ()=>{
        console.log('a1 called')
    };
    exports.a2 = ()=>{
        console.log('a2 called');
        b1()
    };
    

    Or of course refactor your code so that it doesn't use circular dependencies.