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);
}
Related
I'm building a dialogflow agent that uses Airtable as database (library: airtable js)
Everything works fine except I can't get the value "out of" the function in order to send it back to the dialogflow agent.
Function
function showSinglePrice(agent) {
var finalPrice;
var arraySinglePrice = null;
const item = agent.context.get("item"),
place = item.parameters.place,
size = item.parameters.size,
type = item.parameters.type;
base(tablePlaces)
.select({
maxRecords: 10,
view: viewName,
filterByFormula: `AND({type} = "${type}",{size} = "${size}",{place} = "${place}")`
})
.firstPage(function(error, records) {
if (error) {
response.send({ error: error });
} else {
arraySinglePrice = records.map(record => {
return {
price: record.get("price")
};
});
console.log(arraySinglePrice); //this works fine
finalPrice = arraySinglePrice[0].price; //this works fine
return finalPrice;
}
});
agent.add(`I wanted to get the result in here: ${finalPrice}`); //undefined
}
I'm new to asynchronous programming, so I'm probably messing up with the Airtable js promises, but can't figure it out how to get it to work.
Would appreciate any help
EDIT
THANKS #PRISONER FOR THE HELP.
FOR THOSE IN NEED, HERE IS THE WORKING CODE:
function showSinglePrice(agent) {
const item = agent.context.get("item"),
place = item.parameters.place,
size = item.parameters.size,
type = item.parameters.type;
return base(tablePlaces) //defined variable before this function
.select({
maxRecords: 1, //just want 1
view: viewName, //defined variable before this function
filterByFormula: `AND({type} = "${type}",{size} = "${size}",{place} = "${place}")`
})
.firstPage()
.then(result => {
console.log(result);
var getPrice = result[0].fields.price;
agent.add(`the current price is: $ ${getPrice}`); //its working
})
.catch(error => {
console.log(error);
response.json({
fulfillmentMessages: [
{
text: {
text: ["We got the following error..."] //will work on it
}
}
]
});
});
}
You're correct, there are some issues with how you're using Promises. You're using a callback function in your call to firstPage() instead of having it return a Promise. So you could have written that part to look something like this:
.firstPage()
.then( records => {
// Work with the records here
})
.catch( err => {
// Deal with the error
});
Once you're dealing with Promises, everything you want to do must be done inside the .then() block. So you'll need to move the agent.add() in there.
You also need to return the Promise, so Dialogflow knows that theres an asynchronous operation taking place. Since the .then() and .catch() functions return a Promise, you can just return the result of the whole expression. So something like
return base(tablePlaces)
.select(query)
.firstPage()
.then(/*function*/)
.catch(/*function*/);
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});
};
I'm trying to check if a user exists (registered on a json file).
Unfortunately I don't find a valid solution in all Stack Overflow that gives me a simple "true" in a callback.
The version closest to a solution
Experiment V1 :
let userExist;
function check(){
console.log("CHECK!");
return userExist = true;
}
// check(); if this, return true... obvious.
//// check if user exist
server.readFileSync(filepath, 'utf8', (err, data) =>
{
let json = JSON.parse(data),
userlist = json.allusers;
for (let key in userlist)
{
if ( userlist[key].email == req.body.user_email )
{
console.log("FINDED EQUAL");
check(); // return undefined ???
}
}
});
console.log("userExist value : "+userExist);
differently formulated the debugs also appear, but "true" never returns.
note: yes, JSON is read correctly. If everything works inside the readfile, you immediately notice the same emails.
output: "undefined"
Log: total bypassed
Experiment V2 :
In this case (with asynchronous reading) it returns all the debugging (but the "true" remains undefined)
The problem with the asynchronous is that I have to wait for it to check to continue with the other functions.
//// check if user exist
server.readFile(filepath, 'utf8', (err, data) =>
{
let json = JSON.parse(data),
userlist = json.allusers;
for (let key in userlist)
{
if (/* json.allusers.hasOwnProperty(key) &&*/ userlist[key].email == req.body.user_email )
{
console.log("FINDED EQUAL");
check();
}
}
});
var userExist;
function check(userExist){
console.log("CHECK!");
return userExist=true;
}
console.log("userExist value : "+userExist+"");
server listening on: 8080
userExist value : undefined
CHECK!
FINDED EQUAL
Experiment V3 :
after the various suggestions I come to a compromise by using the syntax for the async functions.
This allowed to reach an ordered code, but despite this it is not possible to wait for the results and export them out of the same function (this is because node itself is asynchronous! Therefore it has already gone on!)
using a "message" variable to check if it could return an object I did so:
//simple output tester
var message;
// create a promise
let loopusers = new Promise( (resolve)=>{
server.readFile( filepath, 'utf8',
(err, data) => {
let json = JSON.parse(data),
userlist = json.allusers,
findedequal;
console.log("CHECK USERS IN DB...for "+userlist.length+" elements");
// loop all items
for (let key in userlist)
{
console.log("Analyzed key ::: "+key);
if ( userlist[key].email == req.body.user_email )
{
console.log("CHECK : user isn't free");
findedequal=true;
resolve(true); // return the result of promise
}
else if(key >= userlist.length-1 && !findedequal )
{
console.log("CHECK : User is free ;)");
resolve(false); // return the result of promise
}
}
// call the action
createuser();
});
});
// when promise finished --> start action
async function createuser(message)
{
let userExist = await loopusers;
console.log("userExist: "+userExist);
if(userExist)
{
message = { Server: "This user already exists, Try new e-mail..."};
}
else
{
message = { Server: "Registration user -> completed..."};
}
// return values
return message;
};
It is also possible to use the classic syntax via "then". For exemple:
//simple output tester
var message;
// create a promise
let loopusers = new Promise( (resolve)=>{
...
});
loopusers.then( (response)=>{
...
})
Then I realized that it was easy to simplify even more by calling the functions directly from the initial one:
var message;
// create a promise --> check json items
server.readFile( filepath, 'utf8',
(err, data) => {
let json = JSON.parse(data),
userlist = json.allusers,
findedequal;
console.log("CHECK USERS IN DB...for "+userlist.length+" elements");
for (let key in userlist)
{
console.log("Analyzed key ::: "+key);
if ( userlist[key].email == req.body.user_email )
{
console.log("CHECK : user isn't free");
findedequal=true;
createuser(true); // call direct function whit params true
}
else if(key >= userlist.length-1 && !findedequal )
{
console.log("CHECK : User is free ;)");
createuser(false); // call direct function whit params false
}
}
});
// start action
function createuser(userExist)
{
if(userExist)
{
message = { Server: "This user already exists, Try new e-mail..."};
}
else
{
message = { Server: "Registration user -> completed!"};
}
// return values
return message;
};
debugging is taken and written
the message is lost outside the aSync function
Experiment V4 Final! :
Finally, after many attempts the solution! (Yes... But know it's not Async)
If we allocate in a variable the reading becomes synchronous the whole model and we return to the simple one
let message,
file = server.readFileSync(filepath, 'utf8'), // read sync
json = JSON.parse(file), // now parse file
userlist = json.allusers, // get your target container object
userExist,
findedequal;
console.log("CHECK USERS IN DB...for "+userlist.length+" elements");
for (let key in userlist)
{
console.log("Analyzed key ::: "+key);
if ( userlist[key].email == req.body.user_email )
{
console.log("CHECK : finded equal value on key ["+key+"] - user isn't free");
findedequal=true;
userExist = true;
}
else if(key >= userlist.length-1 && !findedequal )
{
console.log("CHECK : User is free ;)");
userExist = false;
}
}
if(userExist)
{
console.log("└ EXIT TO CHECK --> Can't create user, function stop.");
message = { Server: "This user already exists, Try new e-mail..."};
}
else
{
console.log("└ Exit to check --> New user registration ...");
message = { Server: "Registration user -> completed!"};
}
}
return message;
Now:
It's all sync and all log is perfect
all var is checked
all return... return
** Final conclusions: **
Is it possible to retrieve an ASync variable in node?
As far as I understand so far ... no.
Node is async by its nature, therefore recovering information that is not saved and then recovered from a DB is left behind among the things to do, becoming unrecoverable if you use it as in this post.
However ... if the purpose is to make reading a file synchronous, the answer was simpler than expected.
A special thanks to: Barmar; Austin Leehealey; C.Gochev;
The problem is that you are calling console.log("userExist value : "+userExist+"");
too early. At the moment that you call that line, userExist is not defined yet. This is because the server.readFile() function requires a callback and that callback function is executed once it has read the file. However, reading files often take time and so the program keeps going. It executes console.log("userExist value : "+userExist+""); and then goes back to the callback function and defines userExist as true.
If you want more information on what callbacks are look at the link below. Callbacks are a defining feature of Nodejs and understanding them is essential to any Node website.
https://medium.com/better-programming/callbacks-in-node-js-how-why-when-ac293f0403ca
Try something like this.
let userExist;
function check(){
console.log("CHECK!");
return userExist = true;
}
// check(); if this, return true... obvious.
//// check if user exist
server.readFileSync(filepath, 'utf8', (err, data) =>
{
let json = JSON.parse(data),
userlist = json.allusers;
for (let key in userlist)
{
if ( userlist[key].email == req.body.user_email )
{
console.log("FINDED EQUAL");
check(); // return undefined ???
console.log("userExist value : "+userExist);
}
}
});
My function should assign an employee on a seat if the seat is available. I do not understand why the program doesn't act as synchronous even though I used "await".
In the first lines of the function, the program acts as expected. it waits to get "seats"from the database, then performs the "if(seats.length > 0)"
check and initialises an empty array.
async function AssignSeat(req, res) {
var seats = await connection.SeatEmployees.findAll({
where: {
SeatId: req.body.seat.SeatId
}
})
.catch(err => {
res.status(err.status).json(err)
});
if(seats.length > 0){
var isShared = true;
var employees = [];
await seats.forEach(async function(seat){
var employee = await connection.EmployeesGraph.findAll({
where: {
id: seat.EmployeeGraphId
}
})
.catch(err => {
res.status(err.status).json(err)
});
employees.push(employee);
})
.catch(err => {
res.status(err.status).json(err)
});
employees.forEach(employee => {
if(employee.frequent == true)
isShared = false;
})
if(isShared == true){
//assign user to seat;
}
}
}
My problem is at the 13th line of code, at " await seats.forEach(async function(seat)".
What I want to do is go through each element of "seats", Get the employee assigned to that seat, and push it into the "employees" array.
Only after iterating from all the seats and filling the employees array, I want proceed with the "employees.forEach(employee => {" line.
Instead, what happens is that after calling
-----"var employee = await connection.EmployeesGraph.findAll({ "---- ,the program doesn't wait for sequelize to get the employee from the database and then go to ----"employees.push(employee);"---- , as intended.
It goes to the paranthesis on the line after ----"employees.push(employee);"---- , then I get the error "TypeError: Cannot read property 'catch' of undefined".
Could you please explain why this happens?
The easiest solution is to use an actual for loop instead of forEach for this task. forEach() won't wait to iterate over everything.
try {
for (const seat of seats) {
var employee = await connection.EmployeesGraph.findAll({
where: {
id: seat.EmployeeGraphId
}
})
.catch(err => {
res.status(err.status).json(err)
});
employees.push(employee);
}
} catch (err) {
res.status(err.status).json(err)
}
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.