Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReferenceError: Cannot access 'Player' before initialization

So I've been using ES6 style syntax with import/export on Nodejs with the ESM module loader. Everything has been fine until I started getting an error pertaining to imports.

Here's the error messages:

joseph@InsaneMachine:~/placeholder2/main-server$ npm start

> [email protected] start /home/joseph/placeholder2/main-server
> nodemon --experimental-modules src/index.mjs

[nodemon] 1.19.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node --experimental-modules src/index.mjs`
(node:16942) ExperimentalWarning: The ESM module loader is experimental.
file:///home/joseph/placeholder2/main-server/src/games/game-player.mjs:3
export default class GamePlayer extends Player
                                        ^

ReferenceError: Cannot access 'Player' before initialization
    at file:///home/joseph/placeholder2/main-server/src/games/game-player.mjs:3:41
    at ModuleJob.run (internal/modules/esm/module_job.js:109:37)
    at async Loader.import (internal/modules/esm/loader.js:132:24)
[nodemon] app crashed - waiting for file changes before starting...

Here are the files Player (Base class):

import PasswordHash from 'password-hash';

import GamesService from '../games/games.service.mjs';

import PlayersService from './players.service.mjs';

import QueueingService from '../queueing/queueing.service.mjs';

export default class Player
{
    constructor(object)
    {
        Object.assign(this, JSON.parse(JSON.stringify(object)));
    }

    get id()
    {
        return this._id.toString();
    }

    equals(other)
    {
        if(other.id != null)
            return other.id == this.id;
        return false;
    }

    checkPassword(password)
    {
        return PasswordHash.verify(password, this.password);
    }

    online()
    {
        return PlayersService.consumer.isPlayerOnline(this);
    }

    inQueue()
    {
        return QueueingService.queued(this);
    }

    inGame()
    {
        return GamesService.getActiveGameByPlayer(this) != null;
    }

    reduce()
    {
        return {
            id: this.id,
            username: this.username,
            email: this.email,
            admin: this.admin,
            online: this.online(),
            in_queue: this.inQueue(),
            in_game: this.inGame(),
        };
    }

    static hashPassword(password)
    {
        return PasswordHash.generate(password);
    }

    static schema = {
        username: String,
        password: String,
        email: String,
        email_confirmed: Boolean,
        admin: Boolean,
    }
}

And GamePlayer (Child Class):

import Player from '../players/player.mjs';

export default class GamePlayer extends Player
{
    constructor(player, token)
    {
        super(player);
        this.token = token;
    }
}

And the heirarchy of the project:

src/
 -- games/
 --  -- game-player.mjs
 --  -- ...
    players/
 --  -- player.mjs
 --  -- ...
 -- ...

How can I fix this import issue, unless this is something else?

Edit: I am not using Babel as far as I know, I am using --external-modules provided by Node. Not sure how that works.

like image 360
Joseph Norman Avatar asked Feb 08 '20 00:02

Joseph Norman


People also ask

Can not access before initialization?

The "Cannot access before initialization" error occurs when a variable declared using let or const is accessed before it was initialized in the scope. To solve the error, make sure to initialize the variable before accessing it.

Can t access lexical declaration id before initialization?

The JavaScript exception "can't access lexical declaration `variable' before initialization" occurs when a lexical variable was accessed before it was initialized. This happens within any block statement, when let or const variables are accessed before the line in which they are declared is executed.


2 Answers

I went to the Node.JS forums and asked what could be the issue. Not a babel issue at all, just circular dependencies. For example:

// A.js
import B from './B.js'
export default class A{}
// B.js
import A from './A.js'
export default class B extends A{}

Sorry there wasn't nearly enough information to be able to figure this one out. I got a lot of help on the node.js github and someone looked through my project on github and ended up finding an instance where two modules pointed at each other.

like image 183
Joseph Norman Avatar answered Sep 23 '22 12:09

Joseph Norman


The dependencies in your imports were probably too difficult to resolve, so it gave up, leaving you with Player uninitialized at the point where it is needed to define GamePlayer.

As I mentioned in a comment for another answer, import can be used in a "circular" way, but Node.js can't always untangle the dependencies.

In my case it had no problem with a class in one file and a subclass of that in another file, where both of them import each other, and it's hard to say exactly where it got too complicated, but this is a simplified version of what I had that broke it:

// server.js
import Acorn from './acorn.js';
import Branch from './branch.js';
class Server {
    ...
}

// universe.js
import Acorn from './acorn.js';
import Branch from './branch.js';
import Thing from './thing.js';
export default class Universe {
    things(type) {
       if (Thing.klass[type]) {
           ...
       }
    }
    ...
}

// acorn.js
import Thing from './thing.js';
export default class Acorn extends Thing {
    ...
}

// branch.js
import Thing from './thing.js';
export default class Branch extends Thing {
    ...
}

// thing.js
import Acorn from './acorn.js';
import Branch from './branch.js';
export default class Thing {
    static klass(type) {
        const klass = {acorn: Acorn, branch: Branch};
        ...
        return klass;
    }

    constructor(...) {
        this.type = this.constructor.name.toLowerCase();
        ...
    }
    
    ...
}

I'm using the same code for browsers and server-side Node.js, so I was also transpiling it with Babel, which handled everything fine. But Node may have other constraints that make it more difficult, because, at least as far as importing is concerned, it was on a different track from browsers (and others), and is now trying to bridge the gap. And also the knot may be more tangled than it appears to the naked eye.

In the end I ditched the most circular part of my pattern, which was the part where I refer to the Thing subclasses within Thing itself. I extracted that into a "factory-like" pattern, where the factory knows about the Thing subclasses, but the Thing and its subclasses don't need the factory.

like image 44
cesoid Avatar answered Sep 22 '22 12:09

cesoid