It took me a while to figure out what the problem was, but I'm wondering why it's acting like that.
Using this code, the variables player, players and socket will be undefined, causing errors.
var player = Player(socket.id, socket);
socket.on('joinHost', function(data) {
var gameID = data;
player.gameID=gameID;
var game = GAME_LIST[gameID];
game.players[socket.id]=player;
var players = game.players;
for (var p in players){
var player = players[p];
var socket = player.socket;
socket.emit('playerJoined');
}
});
Avoiding the declarations of variables with same names makes it all work correctly.
var player = Player(socket.id, socket);
socket.on('joinHost', function(data) {
var gameID = data;
player.gameID=gameID;
var game = GAME_LIST[gameID];
game.players[socket.id]=player;
var tempPlayers = game.players;
for (var p in tempPlayers){
var tempPlayer = tempPlayers[p];
var tempSocket = tempPlayer.socket;
tempSocket.emit('playerJoined');
}
});
The interesting part is, when I ran the first code, it says the player in the line player.gameID=gameID is undefined, while if I removed the code which is after player.gameID=gameID, the player was defined. Basically, the code after player.gameID=gameID caused the player to be undefined.
So, why is this happening?
When you declare var player = players[p]; it is declared for the whole function scope (the for loop doesn't has a scope of it's own).
The names in the current scope are evaluated all in the beginning, before executing the function body.
So when function(data) is called, the name player is overridden in that scope even before var gameID = data; is executed.
A minimal example:
> var x = 'foo';
> f = function() { console.log(x); var x = 'bar'; }
> f()
undefined
Javascript moves variables's declaration to the top of the scope they were defined and gives them an undefined initial value but keeps assignment in place. This is called hoisting
Your code is equivalent to :
var player = Player(socket.id, socket);
socket.on('joinHost', function(data) {
var gameID; // undefined
var game; // undefined
var players; // undefined
var player; // undefined
var socket; // undefined
gameID = data;
player.gameID=gameID; // can't set property 'gameID' of undefined
game = GAME_LIST[gameID];
game.players[socket.id]=player; // is undefined since 'player' is undefined at this stage
players = game.players; // undefined
for (var p in players){
player = players[p];
socket = player.socket;
socket.emit('playerJoined');
}
});
Related
I'm trying to use one script for the communal storage of "global" variables, and other scripts can require that script to see those variables, but that appears to not be the right way to do it.
So I have a script called "gamedata.js" that looks like this:
var players = {};
exports.players = players;
In one script:
var gamedata = require('./gamedata');
var players = gamedata.players;
players[player_id] = new player(. . .);
for (var pid in players){
console.log(pid + ' is online.'); // This runs correctly
}
Then, later, in another script (I know this is later; it's actually in a loop).
var gamedata = require('./gamedata');
var players = gamedata.players;
for (var pid in players){
// Doesn't even run once
}
Obviously this isn't the right way to do this. How can I do something like this?
Update:
The information necessary to answer this question was not included in this post. For the second example, when it didn't even run once, it was in a different scope, when "players" did in fact mean []. I'm not accepting a correct answer for this because all of the information I included should work correctly, and therefore there cannot be a solution.
Do not attempt to use globals in node.js. Also note that require will reference a cached object and will not actually re-require the same file more than once.
Here's a pretty generic example of how you might start setting up a card game without using global variables
lib/deck.js
var SUITS = ["H", "C", "D", "S"],
RANKS = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"];
var Deck = module.exports = function Deck() {
this.cards = [1, 2, 3, ..., 52];
this.shuffle();
};
Deck.prototype.shuffle = function shuffle() {
// shuffle this.cards
};
Deck.prototype.dealCard = function dealCard() {
var id = this.cards.shift();
return {id: id, rank: RANKS[id%13], suit: SUITS[id%4]};
};
lib/game.js
var Deck = require("./deck");
var Game = module.exports = function Game(numCards) {
this.numCards = numCards;
this.deck = new Deck();
};
Game.prototype.dealCards = function dealCards(player) {
for (var i=0; i<this.numCards; i++) {
player.cards.push(this.deck.dealCard());
}
};
// ...
lib/player.js
var EventEmitter = require("events").EventEmitter;
var Player = module.exports = function Player(name) {
this.name = name;
this.cards = [];
};
Player.prototype = Object.create(EventEmitter.prototype, {constructor: {value: Player}};
// ...
lib/session.js
var EventEmitter = require("events").EventEmitter,
Game = require("./game"),
Player = require("./player");
var Session = module.exports = function Session(numCards) {
EventEmitter.call(this);
this.game = new Game(numCards);
this.players = [];
this.scores = [];
};
Session.prototype = Object.create(EventEmitter.prototype, {constructor: {value: Session}});
Session.prototype.addPlayer = function addPlayer(player) {
// add the player
this.players.push(player);
// deal the player some cards
this.game.dealCards(player);
// setup event listeners
player.on("score", function(points) {
this.addScore(player, points);
});
player.on("playerTurn", function(event) {
// ...
});
};
Session.prototype.addScore = function(player, points) {
if (this.scores[player.id] === undefined) {
this.scores[player.id] = 0;
}
this.scores[player.id] += points;
};
run.js
var Session = require("./session"),
numCards = 2;
var sessionInstance = new Session(numCards);
sessionInstance.on("addPlayer", function(player) {
this.addPlayer(player);
});
// e.g.,
// setup a net.Server and fire "addPlayer" when a user connects
One way to accomplish what you are trying to do is to use a singleton type object as your "global" memory space and let's say this file is named NodeGlobalVars.js.
exports.NodeGlobalVars = NodeGlobalVars;
function NodeGlobalVars()
{
//only do this if this is the first time the object has been used
if(typeof(NodeGlobalVars.SingleInstance) === "undefined")
{
this.GlobalVariable1 = 1;
this.GlobalVariable2 = 2;
//set to this so we won't create more objects later
NodeGlobalVars.SingleInstance = this;
}
return NodeGlobalVars.SingleInstance; //return the single instance variable
};
Now in other files you want to get that data from and modify it, you can do the following in as many different files as you want:
var NodeGlobalVars= require('../NodeGlobalVars/NodeGlobalVars.js').NodeGlobalVars;
//now get access to the NodeGlobalVars
var Globals = new NodeGlobalVars();
//now log the state of GlobalVariable1 and modify the value of GlobalVariable2
console.log("Global1: " + Globals.GlobalVariable1);
Globals.GlobalVariable2++;
You can modify the data freely from any other file and they will all point back to the same memory. In effect, you have created global memory space for a node application and have done so using a nice convenient like namespace Globals on the front of it to make it apparent that it is global.
Now, whether you should do this or not, that's another question.
I think I'm having a difficult time wrapping my head around the scope of the following code:
var EventEmitter = require('events').EventEmitter, util = require('util');
// Here is the Ticker constructor:
var Ticker = function(time) {
this.time = time;
this.tick = function() {
this.emit('tick');
setTimeout(this.tick(), this.time);
};
}
util.inherits(Ticker, EventEmitter);
var newTicker = new Ticker(1000);
newTicker.on('tick', function() { console.log("TICK"); });
newTicker.tick();
What ends up happening, is that the "tick" gets called many, many times filling up the stack without the setTimeout actually setting the timeout to one second.
Could any explain to me what's going on?
EDIT: I looked a little bit more, and I still couldn't figure out the issue of scope, really.
What I ended up doing was following the solution that was given for the problem, which I tried to solve by way of setTimeout().
The solution given was:
var EventEmitter = require('events').EventEmitter, util = require('util');
// Here is the Ticker constructor:
var Ticker = function(time) {
var self = this;
this.time = time;
setInterval(function() {
self.emit('tick');
}, self.time);
};
util.inherits(Ticker, EventEmitter);
var ticker = new Ticker(1000);
ticker.on('tick', function() { console.log("TICK"); });
It does make more sense to me... What I'm wondering now is: Do inner functions and objects in javascript not keep track of their parent's scope and variables?
Your initial problem is that you were calling this.tick inside your setTimeout:
setTimeout(this.tick(), this.time);
Instead, you want to pass a reference to the this.tick function:
setTimeout(this.tick, this.time);
This fixes the problem for the first loop, but the second call to tick (which comes from the setTimeout) is not bound to the correct scope; so, you can bind the function reference to the current value of this using bind:
setTimeout(this.tick.bind(this), this.time);
(You can also use the self trick if you prefer.)
So, the full working program is:
var EventEmitter = require('events').EventEmitter, util = require('util');
// Here is the Ticker constructor:
var Ticker = function(time) {
this.time = time;
this.tick = function() {
this.emit('tick');
setTimeout(this.tick.bind(this), this.time);
};
}
util.inherits(Ticker, EventEmitter);
var newTicker = new Ticker(1000);
newTicker.on('tick', function() { console.log("TICK"); });
newTicker.tick();
How can I create resuable blank objects in node.js? For example :
In file1 :
var Result = {
error :false,
errorReport :[]
}
exports.Result = Result;
In file 2 :
var result = require('../file1');
exports.doSomething = function(){
var output = result.Result;
output.error = true;
output.errorReport = some_error_array;
}
Now, everytime I invoke doSomething(), it is obvious that the last result will be cached.
But, everytime I invoke doSomething(), i want a fresh object as it is declared in Result without any extra programming (i dont want to reset attributes everytime i do some thing with Result object.
Am I missing any minor detail here ? please help me out
You could make it a class:
exports.Result = function() {
this.error = false;
this.errorReport = [];
};
// later on:
var output = new result.Result();
...
Or even make it fancy so that you can pass arguments:
exports.Result = function(error, errorReport) {
this.error = error === undefined ? false : error;
this.errorReport = errorReport === undefined ? [] : errorReport;
};
// later
var output = new result.Result(true, some_error_array);
I currently have an operation in a module that is blocking, so I'm looking at making this into a child process that I fork instead.
If I want to do that, then I of course need to modify the architecture of my module. The module requires that a dependency is injected by calling the module as a function, passing in the dependency, like so:
var dependency = { name: "Bob" }
require('worker')(dependency)
Then in my worker module:
module.exports = function (dependency) {
// Outputs { name: "Bob" }
console.log(dependency)
}
How can I turn this example into a child process being forked?
When using .fork() you are spinning up a completely separate process, so you are not able to pass around references between the parent and child processes (and are limited to messaging after the process has been created).
An approach not requiring messaging is to pass arguments (in an array) when you fork the process. Although I believe you'll have to stick with simple string/number values (but it looks like this might be enough for you from the code). Eg.:
At top level:
var name = 'bob'
var args = [name];
var childProcess = require('child_process').fork(__dirname + '/worker', args);
In the worker process:
var name = process.argv[2]; //AFIAK elements 0 and 1 are already populated with env info
Update
If you really want to go the messaging route (which I'd hesitate to recommend if you already need to send messages), then you could differentiate between the types of messages something like this (there may be more elegant ways):
At top level:
var childProcess = require('child_process').fork(__dirname + '/worker');
childProcess.send({msgtype:'dependencies', content:dependencies});
//Then to send 'normal' message:
childProcess.send({msgtype:'myothermessagetype', content:'some content'}
In worker process:
process.on('message', function(msg){
if(msg.mtype == 'dependencies') {
var dependencies = msg.content;
//Do something with dependencies
} else if(msg.mtype == 'myothermessagetype') {
var normalmessage = msg.content;
//Do something in response to normal message.
}
});
a.js
var fork = require ("child_process").fork;
var child;
var init = false;
var m = module.exports = {};
m.init = function (o){
if (init) return;
init = true;
child = fork (__dirname + "/child");
child.send ({ init: o });
};
m.print = function (o){
if (!init) return;
child.send ({ msg: o });
};
m.uninit = function (){
if (!init) return;
child.on ("exit", function (){
init = false;
});
child.kill ();
};
child.js
var dependency;
var print = function (o){
console.log (o + dependency.name);
};
process.on ("message", function (o){
if (o.init){
dependency = o.init;
}else{
print (o.msg);
}
});
b.js
var a = require ("./a");
a.init ({ name: "asd" });
a.print ("hi, ");
setTimeout (function (){
a.uninit ();
}, 1000);
Prints: hi, asd
In the main module:
var dependency = {message: 'hello there'};
var args = [JSON.stringify(dependency)];
var child = require('child_process').fork('worker', args);
child.send('sayhello');
child.send('exit');
And in the child process module (worker.js):
var dependency = JSON.parse(process.argv[2]);
process.on('message', function(m){
if(m == 'sayhello') console.log(dependency.message);
else if(m == 'exit') process.exit();
});
Here is the error I'm getting when trying to test a basic Socket.io and Express set up (per the example on the socket.io website):
/Users/scottcorgan/Projects/sevenly/campaigns/node_modules/socket.io/lib/manager.js:659
var socket = this.namespaces[i].socket(data.id, true);
^
TypeError: Object function extend(another) {
var properties = Object.keys(another);
var object = this;
properties.forEach(function (property) {
object[property] = another[property];
});
return object;
} has no method 'socket'
at Manager.handleClient (/Users/scottcorgan/Projects/sevenly/campaigns/node_modules/socket.io/lib/manager.js:659:41)
at Manager.handleUpgrade (/Users/scottcorgan/Projects/sevenly/campaigns/node_modules/socket.io/lib/manager.js:588:8)
at HTTPServer.<anonymous> (/Users/scottcorgan/Projects/sevenly/campaigns/node_modules/socket.io/lib/manager.js:119:10)
at HTTPServer.emit (events.js:88:20)
at Socket.<anonymous> (http.js:1390:14)
at TCP.onread (net.js:334:27)
Appreciate any help I can get, please :)
This problem stems from the fact that you or a library you use are adding functions to Object.prototype.
Thus this code:
Object.prototype.foo = function() {};
Object.prototype.bar = function() {};
var myObj = { x: 1 };
for (var i in myObj) {
console.log(i)
}
will print: x, foo, bar (not necessarily in that order) and not just x as you expect.
In your case this happens in manager.js:
// initialize the socket for all namespaces
for (var i in this.namespaces) {
var socket = this.namespaces[i].socket(data.id, true);
// echo back connect packet and fire connection event
if (i === '') {
this.namespaces[i].handlePacket(data.id, { type: 'connect' });
}
}
This code doesn't expect to encounter the declared: extend key, as you can see from the stack trace of the error:
TypeError: Object function extend(another) {
var properties = Object.keys(another);
var object = this;
properties.forEach(function (property) {
object[property] = another[property];
});
return object;
} has no method 'socket'
the program is actually tried to invoke socket() on the extend function.
See Bob's rant here about adding functions to Object.prototype.
As for the solution, you can either add a conditional statement inside manager.js like so:
// initialize the socket for all namespaces
for (var i in this.namespaces) {
if ('extend' == i) continue; // ADDED
var socket = this.namespaces[i].socket(data.id, true);
// echo back connect packet and fire connection event
if (i === '') {
this.namespaces[i].handlePacket(data.id, { type: 'connect' });
}
}
or you can remove the Object.prototype.extend = function(...) {} declaration, which is my personal preference.
Your this.namespaces[i].socket(data.id, true); does not exist. Do something like console.log(typeof this.namespaces[i].socket(data.id, true)); You'll probably get an undefined
I bet one of the elements of your namespaces array is missing.