ReferenceError: Cannot access 'Player' before initialization
Asked Answered
P

6

41

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.

Paleobotany answered 8/2, 2020 at 0:8 Comment(3)
The ReferenceError means thatPlayer isn't declared before it is attempted to be used. game-player.mjs is trying to extend something that doesn't exist at the point of being interpreted.Viscus
But how would I get it to be declared before? @ViscusPaleobotany
Check out #51860543Baisden
P
68

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.

Paleobotany answered 12/2, 2020 at 15:25 Comment(5)
So how did you fix it? Is there an importOnce functionality?Apochromatic
I had to restructure the code so that there weren't these circular dependencies.Paleobotany
I have a project that has "circular" imports like this all over the place and it had been working fine until one that I just added which is almost identical to the others. The others are actually still working fine. It's my understanding that import resolves these "automatically". I haven't found the problem yet, but I assume there's something else going on with my use of the class names that is preventing it from working.Araceli
Had the same problem, thx for explaining, didn't realize what was going on until I found this.Cocoa
In this example, everything will work if class B is loaded first. All module B dependencies are initialized without a loop. If you load A first, then A is not initialized until B is initialized. B calls (applies extends) A, and A has not yet been initialized and the result is a loop.Misrepresent
A
5

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.

Araceli answered 27/6, 2020 at 12:3 Comment(1)
It's even more interesting here. Let's restore the initialization chain. server.js ->acorn.js ->thing.js->loop(branch.js->thing.js) In this case, I think an acceptable solution is to declare initialization in advance, for example, in the export.js file export {default as Acorn} from './acorn.js'; export {default as Branch} from './branch.js'; and in thing.js import {Acorn,Branch} from './export.js'; and in server.js import {Acorn,Branch} from './export.js';Misrepresent
S
3

To solve the problem you need to understand how the problem occurs. So that's how it is.

After 3 days of warming my head. I found out that:

Nodejs first reads your My_script.js and only after reading will it start executing the code.

Therefore, if your script imports another script.js, then the script.js is already loaded and initialized in memory. He's alive, but he's without his soul.

An example:

// My_A.js
import { var_B } from './My_B.js';
// ---> "My_A.js" called "My_B.js" before declaring the variables for "My_B.js" to use them later.
// ---> See that "My_A.js" has already imported "My_B.js". So "My_B.js" is already initialized in memory and has already started executing itself.
// ---> At this moment "My_B.js" is already alive, but it doesn't know "var_A" yet. Here is the real circular problem!;

const var_A = "A";

export { var_A };

// My_B.js
const var_B = "B";

import { var_A } from './My_A.js';
// ---> "My_A.js" cannot be initialized because it already called "My_B.js" before declaring the variables.;
// ---> "My_B.js" has already been running in memory since the past and is unaware of "var_A" coming from "My_A.js";

console.log("var_A:", var_A); // ---> Ignore "var_A";
console.log("var_B:", var_B);
Seriate answered 2/10, 2023 at 17:50 Comment(0)
M
1
  1. Assigning a module in the initialization phase requires initialization of all module dependencies. If at least one dependency creates a cycle and accesses such dependencies (execution, assignment, apply), then initialization will not occur.
  2. During loops (when modules refer to each other), you cannot use (assignment, execution, launch) such modules at the stage of initialization of these modules.
  3. For the correct module code, you must first:
  • A dependency must be fully initialized before it can be used (execute, assign, apply);
  • Declare a module script (a script that is not executed, including excludes the assignment of other modules);
  • In a separate script, launch its execution or its configuration after initialization;
  • To fix the situation, you need to fix the dependency that creates the loop (for example, create a getter for it);
  • First import modules that use dependencies for initialization Example:
// ./script.js
import B from './B.js';// this it first, because it uses a dependency (extends A).
import A from './A.js';

// ./A.js
import B from './B.js';
export default class A{}
// ./B.js
import A from './A.js';
 export default class B extends A 
 {
 }
  • Use a separate export file for multiple dependencies Example:
// ./export.js
//In the export.js file, the order of declaration matters. Declare independent modules first. Then declare modules with dependencies.
export {default as A} from './A.js'; 
export {default as B} from './B.js';
// ./A.js
import {B} from './export.js';
export default class A {}
// ./B.js
import {A} from './export.js';
export default class B extends A{}

##Examples
The examples clearly show how to solve the problem of loops. The main thing to understand is that module dependencies must be used implicitly during initialization or used after initialization.

./run_script.js

export B from './B.js'; // the first, since it has a dependency A ( extends A)
export A from './A.js';

Working script

./A.js

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

}

./B.js

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

}

not working script (loop)

./A.js

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

}
A.dependency={BClass:B};

./B.js

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

}
B.dependency={AClass:A};

How to fix
./run_script.js

export A from './A.js';
export B from './B.js';

A.dependency={BClass:B};
B.dependency={AClass:A};

./A.js

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

}

./B.js

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

}

Hook

./run_script.js

export A from './A.js';
export B from './B.js';

./A.js

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

}
A.dependency={};
Object.defineProperties(A.dependency,{
   BClass:{
      get(){
         return B;
      }
   }
});

./B.js

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

}
B.dependency={};
Object.defineProperties(B.dependency,{
   AClass:{
      get(){
         return A;
      }
   }
});

Hook2

./init.js

class Init {
    #listeners={};

    trigger(event){
        if(event in this.#listeners){
            let listeners=this.#listeners[event];
            delete this.#listeners[event];
            for(let call of listeners){
                call();
            }
        }
    }
    on(event,call){
        if(!(event in this.#listeners)){
            this.#listeners[event]=[];
        }
        this.#listeners[event].push(call);
    }
}
export default new Init();

./run_script.js

export A from './A.js';
export B from './B.js';

./A.js

import init from './init.js';
import B from './B.js';
export default class A {

}
init.on('init_B',()=>{
    console.log('init_B');
    A.dependency={BClass:B};
    init.trigger('init_A');//Must be called because the listeners might not have been initialized before the core trigger
});
init.trigger('init_A');// core trigger

./B.js

import init from './init.js';
import A from './A.js';
export default class B {

}
init.on('init_A',()=>{
    console.log("init_A");
    B.dependency={AClass:A};
    init.trigger('init_B'); //Must be called because the listeners might not have been initialized before the core trigger
});
init.trigger('init_B');  // core trigger

UPDATEd: Rewrote init.js for comfortable use

class Init {
    static #listeners={};
    #name;
    constructor(name){
        this.#name=name;
    }
    module(name){
        return new Init(name);
    }
    trigger(event){
        if(event===undefined){
            event=this.#name;
        }
        if(event in Init.#listeners){
            let listeners=Init.#listeners[event];
            delete Init.#listeners[event];
            for(let call of listeners){
                call();
            }
        }
        return this;
    }
    on(event,call){
        if(!(event in Init.#listeners)){
            Init.#listeners[event]=[];
        }
        let sanbox=call;
        if(this.#name!==undefined){
            sanbox=()=>{
                call();
                this.trigger(this.#name);
            };
        }
        Init.#listeners[event].push(sanbox);
        return this;
    }
}
export default new Init();

used

import init from './init.js';
import  A from './A.js';

export default class B{
}
B.dependency={};

init.module('B')
.on('A',()=>{
   B.dependency.AClass=A;
})
.on ('Last_Dep',()=>{
//...
})
//...
.trigger(); 

import init from './init.js';
import B from './B.js';
export default class A{
}
A.dependency={};
init.module('A')
.on('B',()=>{
   A.dependency.BClass=B;
}) 
.trigger();


Misrepresent answered 10/12, 2022 at 15:17 Comment(0)
K
1

It helped me - to change the order of re-exports. It is necessary that a component using another component inside itself should be lower in the order of the list of re-exports

Example of a reexport file:

// shared/index.js
export * from './widget'
export * from './button'
...

It helped to change the order to:

// shared/index.js
export * from './button'
export * from './widget'
...

This helped when using imported components outside of react components, example:

// component.jsx
import {Button} from 'shared'
import {Widget} from 'shared'
...

const mapComponents = {Button, Widget}

export const Component ({type}) => {
  const CurrentComponent = mapComponents[type]
  return <CurrentComponent/>
}

I think it has something to do with the fact that the Button was also imported into the Widget

Kendyl answered 19/9, 2023 at 13:47 Comment(1)
Please post this as a comment. If you have more details, then please add and update answer.Ultramicroscopic
B
-3

Remove es2015 from your Babel configuration. class extends native ES6 class and Babel transpiles to ES which is causing the issue most likely

Baisden answered 8/2, 2020 at 0:38 Comment(1)
Sorry, I don't know where to go to remove es2015 from Babel. Is that something I have to edit in the package.jsonPaleobotany

© 2022 - 2024 — McMap. All rights reserved.