3

I am new to JavaScript and trying to make a simple node server. Here is my code:

var activeGames = {}

exports.initialize = function(){
    var gameID = "game12345"
    activeGames.gameID = new Game(gameID, "player1", "player2")
}

I call the initialize function from another module, and I get an error stating that activeGames is undefined. activeGames is at the outermost scope of the file. I tried adding 'this' before activeGames.gameID but that did not fix it. Why is activeGames undefined? Thanks in advance.

EDIT: Here's how I'm calling this code.

In my base index file I have

const handler = require("./request-handler.js")
handler.initialize()

In request-handler.js, I have

var gameManager = require('./game-manager')

exports.initialize = function(){
    gameManager.initialize()
}
16
  • 1
    How are you importing/calling the function in the other module? Can you add that code to your question? Commented Dec 23, 2017 at 23:54
  • 1
    You are not exporting the variable. Commented Dec 23, 2017 at 23:56
  • 1
    @Connor Using global variables is not a good pattern/best practice. Commented Dec 24, 2017 at 0:01
  • 3
    @Connor I cannot reproduce your error. The code you posted works fine for me. Commented Dec 24, 2017 at 0:24
  • 1
    If this is code running in a node module, the above code does NOT result in a global variable. Commented Dec 24, 2017 at 0:31

3 Answers 3

1

JavaScript has lexical scope, not dynamic scope. ref: https://en.wikipedia.org/wiki/Scope_(computer_science)#Lexical_scoping

Lexical scope means that whether a variable is accessible or not depends on where they appear in the source text, it doesn't depend on runtime information.

example:

function foo() {
  var bar = 42;
  baz();
}

function baz() {
  console.log(bar); // error because bar is not in the scope of baz
}

the same problem happens in your code,

var activeGames

is not in scope.

try this variation:

exports.initialize = function(){
    var activeGames = {}
    var gameID = "game12345"
    activeGames.gameID = new Game(gameID, "player1", "player2")
}

A good solution could be to use a class and export it: --THIS CODE IS NOT TESTED--

class gamesManager {
   var activeGames = {}

   initialize() {
      var gameID = "game12345"
      activeGames.gameID = new Game(gameID, "player1", "player2")
   }
}

exports.gamesManager = gamesManager

USE:

const activeGames = require('./game-manager');
const activeGamesInstance = new activeGames.gamesManager();
activeGamesInstance.initialize();
Sign up to request clarification or add additional context in comments.

11 Comments

Ok, but I want to use activeGames in other functions
I can't find a reason to export the activeGames object. I ran his code and it's working perfectly.
I only need to use it in this module. Is there a way I can group the contents of this module other than just keeping it in the same file? For example, in Java I'd make it a class.
i think that a good idea for refactor your code could be to create a cass and export it instead of a function.
@DaniloCalzetta But it's in the scope of the module containing the 'initialize' function expression. Since this variable binding is lexical, the context from where we call that initialize function shouldn't matter, right?
|
1

Need a code sample for this one. I ran this locally and it worked fine, although your code has a big issue which may be a part of your problem. It looks like you want to keep track of multiple games in activeGames. You need to use this syntax instead:

activeGames[gameID] = new Game(gameID, "player1", "player2")

Here's my working code:

index.js:

const handler = require("./request-handler");

handler.initialize('game-1234');
handler.initialize('game-5678');

request-handler.js:

var gameManager = require('./game-manager');

exports.initialize = function(gameID) {
    gameManager.initialize(gameID);
}

game-manager.js:

var activeGames = {};

class Game {
    constructor(id, player1, player2) {
        this.id = id;
        this.player1 = player1;
        this.player2 = player2;
    }
}

exports.initialize = function(gameID) {
    activeGames[gameID] = new Game(gameID, "player1", "player2");

    console.log(`game initialized! ${ Object.keys(activeGames).length } active games`);
}

Running node index results in this:

game initialized! 1 active games
game initialized! 2 active games

Comments

1

When you require a script file in Node.js, it is compiled as part of a function called with named parameters require, module, exports and other exposed variables as arguments1. Variables declared at file level within the required script become function level variables in the enclosing module wrapper and retained inside its closure.

Hence your "global variable" is no such thing: it's a variable defined inside a closure...

An important question then is does the module loader make variables declared in a parent module available to scripts required inside the parent. A quick test shows that the answer is general: no, modules do not have automatic access to variables declared in other modules - those variables are inside closures.

This indicates that to pass variable values to scripts that have been required, generally pass them as argument values to exported functions.

It is also possible to export any javascript value as a property of module.exports from within a required script, or add properties to an exports object after it has been returned from requiring a script. Hence it is technically feasible to pass information up and down between modules by adding properties to exports objects.

Redesigned code has multiple options to

  1. define activeGames at the application level and pass it down as a parameter to modules needing access to it, or

  2. export activeGames from game-manager.js by adding

      exports.activeGames = activeGames
    

    to the end of the file. This will not take care of exporting activeGames out of the parent module request-manager.js for use elsewhere, but it could be a start. Or

  3. define activeGames as a global variable (in node) using

      global.activeGames = {}  // define a global object
    

    Defining global variables is not encouraged as it can lead to collisions (and consequent program failure) between names used by applications, code libraries, future library updates and future versions of ECMAScript. Or,

  4. Define an application namespace object for data global to the application. Require it wherever access to application data is needed:

    • create appdata.js as an empty file. Optionally include a comment:

       // this files exports module.exports without modification
      
    • require appdata.js wherever needed.

      var appData = require('./appdata.js')
      appData.gameData = {}; // for example
      

    This relies on node.js maintaining a cache of previously required modules and does not recompile modules simply because they have been required a second time. Instead it returns the exports object of the previous require.

Happy festive season.


References
1The Node.js Way - How require() Actually Works

2 Comments

Insightful explanation, but does it answer OP's question "Why is activeGames undefined"?
@le_m it does if the error is caused by attempting to access activeGames outside the module it's defined in. It is not a global variable and the question title suggests an assumption that it is. I've asked the OP to clarify in the post.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.