Reputation: 13
I need to require two js objects (Player & Room) into each other files. But when I did, an unexpected error occurred.
app.js:
const Player = require("./Player").Player
let player = new Player()
Player.js:
const Room = require ("./Room").Room
let room = new Room()
const Player = function () {
let room = new Room()
}
exports.Player = Player
Room.js:
const Player = require("./Player").Player
let player = new Player()
const Room = function () {
}
exports.Room = Room
And the error:
/home/mosi/Github/test/Room.js:2
let player = new Player()
^
TypeError: Player is not a constructor
at Object.<anonymous> (/home/mosi/Github/test/Room.js:2:14)
at Module._compile (internal/modules/cjs/loader.js:778:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:789:10)
at Module.load (internal/modules/cjs/loader.js:653:32)
at tryModuleLoad (internal/modules/cjs/loader.js:593:12)
at Function.Module._load (internal/modules/cjs/loader.js:585:3)
at Module.require (internal/modules/cjs/loader.js:692:17)
at require (internal/modules/cjs/helpers.js:25:18)
at Object.<anonymous> (/home/mosi/Github/test/Player.js:1:14)
at Module._compile (internal/modules/cjs/loader.js:778:30)
Upvotes: 1
Views: 2632
Reputation: 1075209
Because they depend on each other, your modules are cyclic; see the Node.js documentation here. As they say, "Careful planning is required to allow cyclic module dependencies to work correctly within an application." :-)
If you can avoid having modules in a cycle, that's usually best.
With ESM, "all" you'd have to do is not use Player
or Room
at the top level of either Player.js
or Room.js
, but with the CJS modules you're using you also have to do more than just that. I'm not an expert on sorting out cycles in CJS modules, but I think the main thing you need to do is not try to grab the Player
or Room
exports immediately. Let the modules finish loading first. For instance:
app.js
:
const Player = require("./Player").Player;
let player = new Player();
Player.js
:
// Import the module exports object, but don't grab its `Room` property yet
const RoomMod = require ("./Room");
// Don't do this at the top level: let room = new Room()
const Player = function () {
// Now it's safe to use the `Room` property
let room = new RoomMod.Room();
};
exports.Player = Player;
Room.js
:
// Get the module exports object, but don't try to get the `Player` property yet
const PlayerMod = require("./Player");
// Don't do this at the top level: let player = new Player()
const Room = function () {
};
exports.Room = Room;
For what it's worth, you don't have to import just the modules and then use the properties later if you use ESM (JavaScript standard modules), because with ESM an imported binding is live. So while you still have to avoid using Player
and Room
at the top level of Room.js
and Player.js
, you can import them without needing the module namespace object (the ESM equivalent of the CJS exports object):
app.js
:
import { Player } from "./Player.js";
let player = new Player();
Player.js
:
import { Room } from "./Room.js";
export const Player = function () {
let room = new Room()
};
Room.js
:
import { Player } from "./Player.js";
export const Room = function () {
};
Upvotes: 2