I seem to have encountered a problem while looping through an array. The loop seems to only execute once, no matter the size of the array. I tried using different methods of looping and the error still persists.
As background information, I'm trying to make a bot with which users can award each other points. Everything else seemed alright. The only issue is that I wish to set up a maximum amount of points one user can give to another in a day, and I'm having problems looping through the array which stores this information.
These are the relevant parts of my code:
var timer = []; //Timer stores the values.
const getTimerSenderIdTable = (id) => {
let found = false;
timer.forEach(function(dat) { // This is the problematic loop.
if (dat.id === id) {
found = dat;
}
})
console.log("loop end, results: " + found);
return found;
};
const timerManager = (senderId, targetId, pointSurp) => { //All arguments are integers.
let d = new Date()
if (getTimerSenderIdTable("date") !== d.getDate()) {
timer = [];
timer.push({"id":"date", "time":d.getDate()});
if (getTimerSenderIdTable("date")) {
if (getTimerSenderIdTable(senderId)) {
console.log("path 1");
} else {
console.log("path 2");
timer.push({"id":senderId, [targetId]:pointSurp});
}
}
} else {
if (getTimerSenderIdTable("date")) {
if (getTimerSenderIdTable(senderId)) {
console.log("path 3");
} else {
console.log("path 4");
timer.push({"id":senderId, [targetId]:pointSurp});
}
}
}
console.log(timer)
};
*Edit:
Thank you for your comments. Here is an example:
Calling timerManager(123456, 654321, 3) will produce the following output:
loop end, results: false
loop end, results: [object Object]
loop end, results: false
path 2
[ { id: 'date', time: 28 }, { '654321': 3, id: 123456 } ]
(This is a repost from comments. My appologies.)
It seems because of this line
if (getTimerSenderIdTable("date") !== d.getDate()) {
timer = [];
This will empty the array and next lines of code will only push single element
as #mbojko has pointed out, you'll want to use the find method for returning the found obj inside getTimerSenderIdTable function, like this
const getTimerSenderIdTable = (id) => {
return timer.find(item => item.id === id});
};
Related
I'm working on a map/reduce script to handle some automated billing processing. I run a search for invoices in the GetInput stage, group them by customer in the Map stage, and then create the payments in the Reduce stage. However, in the Summarize stage, only one key/value pair ever exists. So, I created a dummy test script to play with the functionality and figure it out, and kept running into the same problem.
Here's what I have for the testing script:
define(['N/search'], (search) => {
const getInputData = (inputContext) => {
let filt = [
["trandate","on","3/29/2022"]
, "and"
, ["mainline","is","T"]
];
let cols = [
search.createColumn({ name : "tranid" })
, search.createColumn({ name : "entity" })
, search.createColumn({ name : "total" })
];
let results;
try {
// custom search wrapper
results = getSearchResults("invoice", filt, cols);
}
catch (err) {
log.error({ title : "Error encountered retrieving invoices" , details : err });
}
return results;
}
const map = (mapContext) => {
try {
let data = JSON.parse(mapContext.value);
let output = { key : "" , value : data };
let rand = randomInt(0, 1); // custom random number generator
if (rand === 0) {
output.key = "FAILURE";
}
else {
output.key = "SUCCESS";
}
mapContext.write(output);
}
catch (err) {
log.error({ title : "Map Stage Error" , details : err });
}
}
const reduce = (reduceContext) => {
reduceContext.write({
key : reduceContext.key
, value : JSON.stringify(reduceContext.values)
});
}
const summarize = (summaryContext) => {
summaryContext.output.iterator().each((key, value) => {
log.audit({ title : `summary -- ${key} -- ${typeof value}` , details : value });
});
}
return {getInputData, map, reduce, summarize}
});
By all accounts, the summary logging should have two key entries to report, but only ever has one. In the test I've tried it both by marking values as either SUCCESS or FAILURE in the Map stage and then just passing it through the Reduce stage, and also by passing the values through the Map stage to be marked as SUCCESS or FAILURE in the Reduce stage and then passed on with the invoice record ID as the key. No matter what, the output iterator in the Summarize stage only ever reports back a single key. I've had this work correctly in one particular situation, but for the life of me I can't figure out what's different/wrong.
Any insights? Otherwise the only way I can think of to be able to propagate necessary data is to utilize the 'N/cache' module, which does work pretty well but feels like it should be unnecessary.
My understanding is that you need to return true; from the each() callback function, or it will stop processing.
const summarize = (summaryContext) => {
summaryContext.output.iterator().each((key, value) => {
log.audit({ title : `summary -- ${key} -- ${typeof value}` , details : value });
return true;
});
}
I handle errors in MP script by this way
summarize: function summarize(summarizeContext) {
function handleErrorIfAny(summary)
{
var mapSummary = summary.mapSummary;
var reduceSummary = summary.reduceSummary;
handleErrorInStage('map', mapSummary);
handleErrorInStage('reduce', reduceSummary);
}
function handleErrorInStage(stage, summary)
{
summary.errors.iterator().each(function(key, value){
nLog.error('Failure in key ' + key, value);
return true;
});
}
handleErrorIfAny(summarizeContext);
}
I made a question a minutes ago that I could solve but now I'm having another problem with my code, I have an array of objects lstValid[]and I need each object to be inserted into a table (using a SP) and I though of a way to made it reading documentation but it's not working, maybe it's just a fool mistake but I don't know how to solve it, my mistake is
TypeError: Cannot read properties of undefined (reading 'Id_Oficina')
as you can see, it executes my SP but it says the attribute Id_Oficina is undefined, do you know why is undefined? why is it reading the array but not the attribute?
Here is the function where I create the objects and insert them into the array:
async function calcWeather() {
const info = await fetch("../json/data.json")
.then(function (response) {
return response.json();
});
for (var i in info) {
const _idOficina = info[i][0].IdOficina;
const lat = info[i][0].latjson;
const long = info[i][0].lonjson;
const base = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${long}&appid=${api_key}&units=metric&lang=sp`;
fetch(base)
.then((responses) => {
return responses.json();
})
.then((data) => {
var myObject = {
Id_Oficina: _idOficina,
Humedad: data.main.humidity,
Nubes: data.clouds.all,
Sensacion: data.main.feels_like,
Temperatura: data.main.temp,
Descripcion: data.weather[0].description,
};
// validation and saving data to array
if (myObject.Temperatura < 99)
lstValid.push(myObject);
});
}
}
and here's the function where I insert into DB:
import { Request } from "tedious";
import { TYPES } from "tedious";
function executeStatement() {
calcWeather();
for (var m = 0; m >= lstValid.length; m++) {
const request = new Request(
"EXEC USP_BI_CSL_insert_reg_RegistroTemperaturaXidOdicina #IdOficina, #Humedad, #Nubes, #Sensacion, #Temperatura, #Descripcion",
function (err) {
if (err) {
console.log("Couldn't insert data: " + err);
}
}
);
request.addParameter("IdOficina", TYPES.SmallInt, lstValid[m].Id_Oficina);
request.addParameter("Humedad", TYPES.SmallInt, lstValid[m].Humedad);
request.addParameter("Nubes", TYPES.SmallInt, lstValid[m].Nubes);
request.addParameter("Sensacion", TYPES.Float, lstValid[m].Sensacion);
request.addParameter("Temperatura", TYPES.Float, lstValid[m].Temperatura);
request.addParameter("Descripcion", TYPES.VarChar, lstValid[m].Descripcion);
request.on("row", function (columns) {
columns.forEach(function (column) {
if (column.value === null) {
console.log("NULL");
} else {
console.log("Product id of inserted item is " + column.value);
}
});
});
connection.execSql(request); // connection.execSql(RequestB);??
}
request.on("requestCompleted", function (rowCount, more) {
connection.close();
});
}
I also tried sending them this way but doesn't work either:
request.addParameter("IdOficina", TYPES.SmallInt, lstValid[m].myObject.Id_Oficina);
The problem seems to be a bad condition at the line of
for (var m = 0; m >= lstValid.length; m++) {
This loop initializes m with 0 and increments it while it's greater or equal with the number of elements lstValid has. Since Javascript is a 0-indexed language, lstValid.length is always an invalid index. Valid indexes fulfill this formula
0 <= valid index < lstValid.length
Since your condition checks whether the index is invalid and only then iterates the loop, it will error out at the first iteration if lstValid is empty and it will not execute at all when lstValid is not empty.
Fix
for (var m = 0; m < lstValid.length; m++) {
Explanation
Your error came from the fact that lstValid.length was 0, m was 0 and your code attempted to process member fields of the first element of an empty array. Since there was no first element in the speicifc case, it errored out.
I've got two pages I'm working on, and both return an array of objects. When I use the following code, the new results work:
this.adminService.waiversGetAll()
.subscribe((data: Waiver[]) => {
this.waivers = data;
this.waivers.forEach((e) => {
if(e.has_signed === true) {
e.url = `View`
} else {
e.url = `${e.message}`;
}
return e;
});
console.log(this.waivers);
})
}
But when I try to do the same thing with a different array (where I need to update the values of an array nested inside) I don't get updated values:
this.adminService.GetUnsignedWaivers()
.subscribe((data: Player[]) => {
console.log("data",data);
data.forEach(e => {
let record: Object = {};
for(let i = 0; i < e.waivers.length; i++) {
console.log(e.waivers[i].has_signed);
if (e.waivers[i].has_signed === true) {
e.waivers[i].url = e.waivers[i].signatureUrl;
console.log(e.waivers[i].url);
e.waivers[i].message = "View Waiver";
} else {
e.waivers[i].url = e.waivers[i].url;
e.waivers[i].message = e.waivers[i].message;
}
console.log(e.waivers[i].message);
return;
};
return e;
});
this.size = this.players.length;
console.log(this.players);
})
}
When I look at the console.log of e.waivers[i].has_signed, the data is correct, but after that it's not right.
What do I have to do to make this work? I've tried using a for loop inside the foreach, and a bunch of other stuff.
The data supplied to the loop provides info like:
{
buyer: "email#someaddress.edu"
event: "COED A"
field: "Main"
net: null
player: {shirtSize: null, avp_id: 12345678, adult: true, …}
team: null
waivers: [{
email: "someemail#gmail.com",
has_signed: true,
message: "Liability Waiver",
signatureUrl: "https://somelink.pdf",
url: "https://somelink.com/somekeyidentifier"
}
IF the player has signed the waiver, there will be a signatureUrl field and the message should say "View Waiver" instead of the message telling me what type of waiver they will sign. I want the url to be set to signatureUrl if they signed, so I can use it in a table that doesn't like manipulation of data.
A visual of what is returned in my table:
All I get is 1600 records showing the url as though everyone hasn't signed, but when I console.log has_signed in the inner loop, it's showing TRUE for the ones that should show a signatureUrl instead.
Quickly looking at it, you have a return statement within your for loop, which would stop it from running after the first iteration.
First of all drop all the return statements in your code. Next, use map instead of forEach as the former returns you the new manipulated array and the latter is used just for iteration purpose.
Your code within subscribe then becomes:
data.waivers = data.waivers.map((waiver) => {
if (waiver.has_signed) {
// your logic goes here...
waiver.url = waiver.signatureUrl;
waivers.message = "View Waiver";
}
// No else is required as you are just reassigning with same values
});
this.playerDetails = data;
At last bind this modified data in your template.
I have following function:
exports.onDataAdded = functions.database.ref('/Lager/Shafts/Rescue/582/582001').onWrite((change, context) => {
if (change.before.exists()) {
return null;
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
const original = change.after.val();
return change.after.ref('/Lager/Shafts/Rescue/583/583001').set(original);
});
I am trying to keep the count of product 1 equal to the count of product two (Can't put it in the same ID for several reasons). It executes the function and says the status is ok but does not update the new value of product 2.
What am I missing?
Please try this, Your function is exiting without executing the update.
exports.onDataAdded = functions.database.ref('/Lager/Shafts/Rescue/582/582001').onWrite((change, context) => {
if (change.after.exists()) {
const original = change.after.val();
return admin.database().ref('/Lager/Shafts/Rescue/583/583001').set(original);
}
// Exit when the data is deleted.
if (!change.after.exists()) {
return null;
}
});
This seems like a noop:
exports.onDataAdded = functions.database.ref('/Lager/Shafts/Rescue/582/582001').onWrite((change, context) => {
if (change.before.exists()) {
return null;
}
Or more precisely: it will only get past this code when you delete /Lager/Shafts/Rescue/582/582001, which is not what you seem to be trying. My guess is that you meant the inverse in your check:
if (!change.before.exists()) {
return null;
}
I have upvote and downvote function's which are performing transactions and correctly manipulating the vote count in my database of sports player names.
Votes are cast as 1's and -1's. Then the math is done to total the player's vote count and put in the database as votes
Each time a vote is cast, I would like a function or piece of code to look through all the names in players and assign a number to each name depicting their rank among everyone in the database (based on their votes from most to least) (I.e. James has 10 upvotes and 0 down votes (votes = 10), he's rank 1. John has 10 upvotes and 1 downvote (votes = 9) and is rank 2. If I upvote John, I should refresh the page and see they are tied at 1. This works to a degree with my current code below, but once I start adding more names via the input and do some upvoting, downvoting, and retracting my votes, the voteCount variable gets all whacky and the ranks go way off course. I'm sure there's an easier and or better way to do this.
orderedPlayersRank is an array that sorts the players by votes, with the best first and worst last. So my #1 ranked person should always be first in the orderedPlayersRank array.
global vars
let prevPlayerVotes = 0
let rankCount = 1
//RANKING CODE
//orderedPlayersRank sorts players from highest votes to lowest
orderedPlayersRank.map((player) => {
this.database.child(player.id).transaction(function(player){
if (player.votes >= prevPlayerVotes) {
prevPlayerVotes = player.votes
player.rank = rankCount
} else if (player.votes < prevPlayerVotes) {
rankCount++
player.rank = rankCount
prevPlayerVotes = player.votes
} else {
console.log("Rank calculation error.")
}
return player;
})
})
Here's my complete upvote function just for reference. I'm putting the above code in where I have the //ranking functionality comment toward the bottom. In that spot, the ranking code is run anytime a valid vote is cast. I would be putting the same code in the downvote function as well.
upvotePlayer(playerId) {
const players = this.state.players;
const orderedPlayersRank = _.orderBy(players, ['votes'], ['desc'])
if (this.state.user) {
let ref = firebase.database().ref('/players/' + playerId + '/voters');
ref.once('value', snap => {
var value = snap.val()
if (value !== null) {
ref.child(this.uid).once('value', snap => {
if (snap.val() === 0 || snap.val() == null) {
ref.child(this.uid).set(1);
this.database.child(playerId).transaction(function(player) {
if (player) {
player.votes++
}
return player;
})
} else if (snap.val() === -1) {
ref.child(this.uid).set(1);
//Added vote balancing
this.database.child(playerId).transaction(function(player) {
if (player) {
player.votes++
player.votes++
}
return player;
})
} else if (snap.val() === 1) {
ref.child(this.uid).set(0);
//Added vote balancing
this.database.child(playerId).transaction(function(player) {
if (player) {
player.votes--
}
return player;
})
} else {
console.log("Error in upvoting. snap.val(): " + snap.val())
}
})
} else {
ref.child(this.uid).set(1);
this.alertUpVote()
//Added vote balancing
this.database.child(playerId).transaction(function(player) {
if (player) {
player.votes++
console.log("Player added")
}
return player;
})
}
});
//ranking functionality here
} else {
this.alertNotLoggedIn()
console.log("Must be logged in to vote.")
}
}
As I said, the upvote function is working fine. I'm just looking for some advice on the ranking feature I'm struggling with. I appreciate any help and can supply any other relevant code
So transactions can be run multiple times before completion if the data changes before the transaction is resolved. This can cause any variables outside the scope to become out of sync (i.e. rankCount and prevPlayerVotes). Another reason might be that you are looping over the orderedPlayersRank and returning a Promise for each call to transaction. This will cause prevPlayerRank and rankCount to be read/modified simultaneously instead of sequentially as I assume you are expecting.
One solution could just be to use orderByChild('votes') on the list and use the index paired with checking the previous value to determine rank at display time or set the rank when changes are made to votes (either by Firebase Function, or a watcher).
Ex. (Firebase Function)
export var rank = functions.database.ref('players/{playerId}/votes')
.onUpdate((change, context) => {
// list by 'votes' in ascending order
var orderedListRef = change.after.ref.root.child('players').orderByChild('votes')
var oldVotes = change.before.val()
var newVotes = change.after.val()
var notChanged = 0
var changeRank = 0
// went higher in the list so bump every player passed by 1
if (newVotes > oldVotes) {
// Range: [oldVotes, newVotes]
orderedListRef = orderedListRef.startAt(oldVotes).endAt(newVotes)
changeRank = 1
notChanged = newVotes
} else {// went lower in the list so bump every player passed by -1
// Range: [newVotes, oldVotes]
orderedListRef = orderedListRef.startAt(newVotes).endAt(oldVotes)
changeRank = -1
notChanged = oldVotes
}
return orderedListRef.once('value')
.then((ss) => {
var promises = []
var playersPassed = 0
// IMPORTANT: must use `forEach` to ensure proper order
ss.forEach((playerSS) => {
if (playerSS.key === context.params.playerId) {
return
}
playersPassed += 1
if (playerSS.child('votes').val() === notChanged) {
return
}
// use transaction to ensure proper number of bumps if multiple changes at once
promises.push(playerSS.child('rank').ref.transaction((rank) => {
return rank + changeRank
}))
})
// use transaction to adjust rank by players passed
promises.push(change.before.ref.parent.child('rank')
.transaction((rank) => {
return rank - playersPassed * changeRank
}))
return Promise.all(promises)
})
})
Initialization example
export var initRank = functions.database.ref('players/{playerId}/votes')
.onCreate((snapshot, context) => {
// list by 'votes' in ascending order
return Promise.all([
snapshot.ref.root
.child('players')
.orderByChild('votes')
.startAt(snapshot.val())
.once('value')
.then((ss) => {
return snapshot.ref.parent.child('rank').transaction((rank) => {
if (rank) {
return rank + ss.numChildren
}
return ss.numChildren
})
}),
snapshot.ref.root
.child('players')
.orderByChild('votes')
.endAt(snapshot.val()-1)
.once('value')
.then((ss) => {
var promises = []
ss.forEach((playerSS) => {
promises.push(playerSS.child('rank').ref.transaction((rank) => {
if (rank) {
return rank + 1
}
})
})
return Promise.all(promises)
})
])
})
With this approach, you will need to set the rank of newly created players to the highest rank. Hope this helps!