I am a novice here, I apologize for any ignorance. I am trying to create a family feud game for personal use using node.js and express as the server and socket.io for communication between clients. I ran into an issue with this particular function where if there are less than 5 players it will include all but the last one in the socket message. For example if there are 3 players it seem to be running the player4 else statement (for when blank) before completing the player3 variable assignment. I have tried researching it and found promises and async/await but I don't understand it enough to make it work for my situation or if it would even apply. This is a lot of nested if statements. I feel like a loop would be better but not sure how to do it properly. Any help and suggestions are much appreciated.
if (data.teamNum == 'team1') {
team1.teamNum = 'team1';
fs.access('./public/images/players/' + data.player1, function(error) {
if (error) {
console.log(data.player1 + " icons do not exist.")
team1.player1pic = "default"
} else {
console.log(data.player1 + " icons exist.")
fs.readdir('./public/images/players/' + data.player1, (error, files) => {
team1.player1pic = files; // return the number of files
console.log(data.player1 + " has " + team1.player1pic + " pics.");
});
}
if (data.player2 != 'blank') {
fs.access("./public/images/players/" + data.player2, function(error) {
if (error) {
console.log(data.player2 + " icons do not exist.")
team1.player2pic = "default"
} else {
console.log(data.player2 + " icons exist.")
fs.readdir('./public/images/players/' + data.player2, (error, files) => {
team1.player2pic = files; // return the number of files
console.log(data.player2 + " has " + team1.player2pic + " pics.");
});
}
if (data.player3 != 'blank') {
fs.access("./public/images/players/" + data.player3, function(error) {
if (error) {
console.log(data.player3 + " icons do not exist.")
team1.player3pic = "default"
} else {
console.log(data.player3 + " icons exist.")
fs.readdir('./public/images/players/' + data.player3, (error, files) => {
team1.player3pic = files; // return the number of files
console.log(data.player3 + " has " + team1.player3pic + " pics.");
});
}
if (data.player4 != 'blank') {
fs.access("./public/images/players/" + data.player4, function(error) {
if (error) {
console.log(data.player4 + " icons do not exist.")
team1.player4pic = "default"
} else {
console.log(data.player4 + " icons exist.")
fs.readdir('./public/images/players/' + data.player4, (error, files) => {
team1.player4pic = files; // return the number of files
console.log(data.player4 + " has " + team1.player4pic + " pics.");
});
}
if (data.player5 != 'blank') {
fs.access("./public/images/players/" + data.player5, function(error) {
if (error) {
console.log(data.player5 + " icons do not exist.")
team1.player5pic = "default"
} else {
console.log(data.player5 + " icons exist.")
fs.readdir('./public/images/players/' + data.player5, (error, files) => {
team1.player5pic = files; // return the number of files
console.log(data.player5 + " has " + team1.player5pic + " pics.");
console.log('sending pics');
feud.in(data.room).emit('teampics', team1);
});
}
});
} else {
console.log('sending pics');
feud.in(data.room).emit('teampics', team1);
}
});
} else {
console.log('sending pics');
feud.in(data.room).emit('teampics', team1);
}
});
} else {
console.log('sending pics');
feud.in(data.room).emit('teampics', team1);
}
});
} else {
console.log('sending pics');
feud.in(data.room).emit('teampics', team1);
}
});
}
I hope the code is self explanatory. I haven't tested the getPlayerPic function. I used callback('default') for the test.
If you have to write same code more then twice always make it a function which you run multiple times. The code will be much easier to read as well as easier to find the problem.
// function to get the player picture
function getPlayerPic(player, callback) {
//return callback('default' + player);
fs.access('./public/images/players/' + player, function(error) {
if (error) return callback('default');
fs.readdir('./public/images/players/' + data.player1, (error, files) => {
callback(files);
});
});
};
// test data
var data = {
teamNum: 'team1',
player1: 'p1',
player2: 'p2',
player3: 'p3',
player4: 'blank',
player5: 'blank',
}
var team1 = {};
if (data.teamNum == 'team1') {
team1.teamNum = 'team1';
var players = Object.keys(data).filter(p => p.startsWith('player')).reverse();
// players = [ 'player1', 'player2', 'player3', 'player4', 'player5' ];
function next(callback) {
var p = players.pop(); // pop an item from players
console.log('p', p);
if (p && data[p] && data[p] !== 'blank') // if it exists and is not blank
getPlayerPic(data[p], function(pic){
team1[p + 'pic'] = pic;
next(callback);
});
else // we are done here
callback();
};
next(function(){
console.log('sending pics', team1);
/*
sending pics { teamNum: 'team1',
player1pic: 'defaultp1',
player2pic: 'defaultp2',
player3pic: 'defaultp3' }
*/
});
}
Here's a more functional approach to the same problem.
(If you become enamored of functional programming, check out JS Allongé.)
Caveat rogantis: This code is completely untested, and I'd bet money it will require debugging. I didn't look into how fs.access and fs.addredir work so it may also need to be refactored to make the callbacks legal.
const { teamNum, room, player1, player2, player3, player4, player5 } = data;
const playersList = [player1, player2, player3, player4, player5];
let done;
// We're only dealing with team1 for now
const team1 = {};
let team = team1;
team.teamNum = teamNum;
// Assigns icons for each player (stops if a blank player is found)
for(let player of playersList){
assignIcons(player);
if(isLastPlayer(player)){
break;
}
}
// Primary functions
function assignIcons(player){
fs.access(getPath(player), accessCallback(error, player));
}
function accessCallback(error, player){
if (error) {
// Logs "player has no icons" and assigns default
logPlayerHasIcons(player, false);
assignIconCount(player, "default");
}
else {
// Logs "player has icons" and gets the count
logPlayerHasIcons(player, true);
fs.readdir(getPath(player), readdirCallback(error, fileCount, player);
}
}
function readdirCallback(error, fileCount, player){
// Assigns the icon count for this player
if(error){ console.log(error); }
assignIconCount(player, fileCount);
logIconCount(getPlayerNum(player), fileCount);
// Emits pics if done
if(isLastPlayer(player)){
emitPics();
}
}
function emitPics(){
console.log('sending pics');
feud.in(room).emit('teampics', team);
}
function assignIconCount(player, val){
const
playerNum = getPlayerNum(player),
playerPicPropName = `player${playerNum}pic`;
team[playerPicPropName] = val;
}
// Supporting functions
function isLastPlayer(player){
const playerNum = getPlayerNum();
return (
(playerNum == 5) ||
(playersList[playerNum] == "blank") // playerNum = index of next player
);
}
function getPlayerNum(player){
return playersList.indexOf(player) + 1;
}
function getPath(player){
return "./public/images/players/" + player;
}
function logPlayerHasIcons(player, doesHaveIcons){
console.log(`${player} ${(doesHaveIcons ? "has" : "has no")} icons`);
}
function logIconCount(playerNum, count){
console.log(`player${playerNum} has ${count} pics`);
}
The following is my javascript and receiving an
Expected '(' error
in console.
//GLOBALS
var _debug = false;
//
function tryParseInt(accountNumber) {
if (_debug) {
console.log("Entering tryParseInt: " + new Date().toTimeString());
}
try{
//var retValue = defaultValue;
if(accountNumber !== null) {
if(accountNumber.length > 0) {
if (!isNaN(accountNumber)) {
//retValue = parseInt(accountNumber);
return true;
}
}
}
return false;
}
catch{
console.log("Failed at tryParseInt:");
console.log(err);
console.log("---------------------------------");
}
}
I can't for the life of me figure why. Any extra eye will help.
this should be
catch (error) {
}
I need to decrement a counter (named credits) stored in Firebase real time database.
To decrement the counter I do like this:
var ref = admin.database().ref('licenseCredits/' + name + '/credits');
ref.transaction( (value) => {
if (value === null) {
return 0;
} else if (typeof value === 'number') {
return value - 1;
} else {
console.log('The counter has a non-numeric value: ');
}
});
The credits field is being correctly decremented.
I put this code into a callable function but I don't know how to return the decremented value to the caller. If I simply return the ref.transaction result I get a "Unhandled RangeError exception".
As per the documentation below, the there should be a onComplete function implemented.
https://firebase.google.com/docs/reference/js/firebase.database.Reference#transaction
var ref = admin.database().ref('licenseCredits/' + name + '/credits');
ref.transaction( (value) => {
if (value === null) {
return 0;
} else if (typeof value === 'number') {
return value - 1;
} else {
console.log('The counter has a non-numeric value: ');
}
}, function(error, committed, snapshot) {
if (error) {
console.log('Transaction failed abnormally!', error);
} else if (!committed) {
console.log('We aborted the transaction.');
} else {
console.log('Success!');
}
console.log("Credit data: ", snapshot.val());
});
At the end I found a way to tackle the problem taking into account #chris answer.
I used a javascript promise implemented using the 'kew' library.
Here is the working code:
var qTrans = Q.defer();
var ref = admin.database().ref('licenseCredits/' + name + '/credits');
var credits = 0;
ref.transaction( (value) => {
if (value === null) {
// the counter doesn't exist yet, start at one
return 1;
} else if (typeof value === 'number') {
// increment - the normal case
return value + 1;
} else {
// we can't increment non-numeric values
console.log('The counter has a non-numeric value: ' + JSON.stringify(value));
// letting the callback return undefined cancels the transaction
}
}, (error, committed, snapshot) => {
if (error) {
console.log('Transaction failed abnormally!', error);
qTrans.reject(error);
} else if (!committed) {
console.log('We aborted the transaction.');
qTrans.reject(error);
} else {
console.log('Success!');
console.log("Credit data: ", snapshot.val());
qTrans.resolve(snapshot.val());
}
});
return qTrans.promise;
So, a quick overview, this function is part of a larger app that ingests JSON data and prepares it to be rendered by Handlebars, which is then used for generating a PDF. This particular function has been giving me grief, as from my understanding of how async/await works, the data should be returned by the return returnArray at the bottom of the function. This however does not happen, and instead the empty array is returned. Could anyone offer insight as to why this is? (N.B. I've checked the data is present in iarr when it gets pushed, it's as though the return statement gets fired before the for loop has started.)
async function getPackageItem(item) {
try {
let returnArray = []
if (fs.existsSync(__dirname + "/../json/" + item.sku + ".json")) {
var file = fs.readFileSync(__dirname + "/../json/" + item.sku + ".json")
} else {
var file = fs.readFileSync(__dirname + "/../json/box.json")
}
const tb = JSON.parse(file);
for (var a = 0; a < item.quantity; a++) {
let iarr = [];
if (tb) {
tb.forEach(function(entry) {
ShopifyAuth.get('/admin/products/' + entry.product_id + '.json', (err, productData) => {
if (!err) {
ShopifyAuth.get('/admin/products/' + entry.product_id + '/metafields.json', (err, metafieldData) => {
if (!err) {
var itemObject = {};
var metaCounter = 0;
metafieldData.metafields.forEach(function(metadata) {
switch(metadata.key) {
case "notes": {
itemObject.wm_notes = metadata.value;
metaCounter++
break;
}
case "title": {
itemObject.title = metadata.value;
metaCounter++
break;
}
case "vintage": {
itemObject.year = metadata.value;
metaCounter++;
break;
}
case "shelfid": {
itemObject.shelf_id = metadata.value;
metaCounter++;
break;
}
case "bottleprice": {
itemObject.bottle_price = metadata.value;
metaCounter++;
break;
}
default: {
metaCounter++;
break;
}
}
if(metaCounter === metafieldData.metafields.length) {
itemObject.vendor = productData.product.vendor;
if (itemObject.title == undefined) {
itemObject.title = productData.product.title
}
if (itemObject.wm_notes == undefined) {
itemObject.wm_notes = " "
}
if (itemObject.year == undefined) {
itemObject.year = "Unspecified"
}
if (itemObject.shelf_id == undefined) {
itemObject.shelf_id = "N/A"
}
if (productData.product.images[1] == undefined) {
if (productData.product.images[0]) {
itemObject.logo = productData.product.images[0].src;
} else {
itemObject.logo = '';
};
} else {
itemObject.logo = productData.product.images[1].src;
}
itemObject.quantity = item.quantity;
iarr.push(itemObject)
if(iarr.length == tb.length) {
returnArray.push(iarr);
}
}
});
} else {
throw Error('Error retrieving product metadata');
}
})
} else {
throw Error('Error retrieving product data');
}
})
})
} else {
throw Error('Error loading JSON for specified box');
}
}
return returnArray;
} catch (e) {
console.log(e)
}
}
Edit: That's what I get for writing code at 3am, not sure how I missed that. Thanks for your feedback.
You marked your function async but you're not using await anywhere inside of it so you're not getting any of the benefits of using async. It doesn't make your function magically synchronous, you still have to manage asynchronicity carefully.
If ShopifyAuth.get supports returning a promise then await on the result instead of passing callbacks and your code will work, otherwise construct a Promise, do the async stuff in the promise, and return the promise from the function.
async function getPackageItem(item) {
let result = new Promise((resolve, reject) => {
// all your ShopifyAuth stuff here
if (err) {
reject(err);
}
resolve(returnArray);
});
return result;
}
I think that I am an idiot. What is happening here?? Why there is no i:0 or i:1, just only the last one? It shows that it loops everything and just after looping it tries to save and it is saving the same last object many time and after that I'll get error 500, duplicate key in DB. Is it even possible to save objects inside the for loop :) in AngularJS?
In console.log:
reasonList.length: 2
rma.js: 284 i: 2
rma.js: 285 defectdescDefectdescId: 2
rma.js: 286 returnreasonId: 1
rma.js: 287 rmaId: 15
code:
savedRma = rmaService.save({}, rma);
savedRma.$promise.then(function (result) {
$scope.rma = result;
console.log('result.rmaID--------->' + result.rmaId);
saveReturnReason(result.rmaId);
}, function (error) {
alert('Error in saving rma' + error);
});
$location.path('/rma-preview/' + $scope.rma.rmaId);
rmaDataService.setRma($scope.rma);
}
}; // ELSE CREATE RMA END
function saveReturnReason(rmaId) {
for (var i = 0; i < $scope.reasonList.length; i++) {
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.defectdescDefectdescId = $scope.reasonList[i].defectdescId;
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.returnreasonId = $scope.reasonList[i].returnreasonReturnreasonId.returnreasonId;
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.rmaId = rmaId;
savedRmaHasDefectdesc = rmaDefectSvc.save({}, $scope.rmaHasDefectdesc);
savedRmaHasDefectdesc.$promise.then(function (response) {
$scope.savedRmaHasDefectdesc = response;
console.log('i: ' + i)
console.log('defectdescDefectdescId:' + response.rmaHasDefectdescPK.defectdescDefectdescId);
console.log('returnreasonId:' + response.rmaHasDefectdescPK.returnreasonId);
console.log('rmaId:' + response.rmaHasDefectdescPK.rmaId);
}, function (error) {
alert('Error in saving reasons' + error);
});
} // For loop ending
};
UPDATE FOR forEach
I updated for loop to forEach. Same result, no luck. Still not going to promise.then in first each and then tries to save the last reason multiple times.
function saveReturnReason(rmaId) {
$scope.reasonList.forEach(function(reason){
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.defectdescDefectdescId = reason.defectdescId;
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.returnreasonId = reason.returnreasonReturnreasonId.returnreasonId;
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.rmaId = rmaId;
console.log('rmaId: ' +rmaId+': returnReasonId: ' +reason.returnreasonReturnreasonId.returnreasonId +' defectID: '+reason.defectdescId);
savedRmaHasDefectdesc = rmaDefectSvc.save({}, $scope.rmaHasDefectdesc);
// At the first loop, never comes to .then
savedRmaHasDefectdesc.$promise.then(function (response) {
$scope.savedRmaHasDefectdesc = response;
}, function (error) {
alert('Error in saving reasons' + error.status);
});
});// ForEach ending
};
Scope the i so that it's only available in the loop:
for (var i = 0; i < $scope.reasonList.length; i++) {
(function(i){
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.defectdescDefectdescId = $scope.reasonList[i].defectdescId;
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.returnreasonId = $scope.reasonList[i].returnreasonReturnreasonId.returnreasonId;
$scope.rmaHasDefectdesc.rmaHasDefectdescPK.rmaId = rmaId;
savedRmaHasDefectdesc = rmaDefectSvc.save({}, $scope.rmaHasDefectdesc);
savedRmaHasDefectdesc.$promise.then(function (response) {
$scope.savedRmaHasDefectdesc = response;
console.log('i: ' + i)
console.log('defectdescDefectdescId:' + response.rmaHasDefectdescPK.defectdescDefectdescId);
console.log('returnreasonId:' + response.rmaHasDefectdescPK.returnreasonId);
console.log('rmaId:' + response.rmaHasDefectdescPK.rmaId);
}, function (error) {
alert('Error in saving reasons' + error);
});
})(i)
} // For loop ending
The problem as other people have mentioned is that your loop finishes before the promise is done, so by the time you console log the i is already updated.