简体   繁体   中英

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

> main-server@1.0.0 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.

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.

The dependencies in your import s 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.

  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();


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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM