Behavior I'm getting from the code shown in the attached print screen: my FOR loop executes and then the two 'LET' (i.e. let path_get... and let get = https.get...).
Problem: when the 2nd LET is executed, the code goes back to the FOR, without ever executing the function storeCarData.
My intention: every time that the second LET executes (GET call), I want storeCarData to to be executed taking as inputs the result of this GET. This is probably an async/await question but I need your help in pointing out where to insert the asyncs/awaits if possible. I need to execute ALL of the functions in sequence (i.e one executes only after the previous one has returned).
Could you please help me here? In summary: when I execute the GET I want storeCarData to run, using the GET outputs as inputs for the function.
Thank you in advance.
Diego
PS: full code below
const https = require('https');
const url = require('url');
exports.handler = (req, res) => {
var page_number = (typeof(req.body.state) == 'undefined' || typeof(req.body.state.page_number) == 'undefined') ? 1 : req.body.state.page_number+1;
var kbb_vehicles = [];
var price_type = [2,3];
var price_description = ['C2C','C2B'];
// PRICE TYPES
// "ID": 1, "Typical Dealer Asking Price"
// "ID": 2, Private Party Value (for owners)"
// "ID": 3, "Trade-In Value"
// "ID": 4, "Private Party Value (for shoppers)"
var epoch = new Date().getTime();
console.log(`Date time is ${epoch}`);
var path_get_all_vehicles = `/Gps/GetAllVehicle?securityToken=${req.body.secrets.securityToken}&language=pt-BR&withCount=true&pageNumber=${page_number}&records=100`;
// GET ALL CARS
let get = https.get(
{
hostname: 'api.kbb.com.br',
path: path_get_all_vehicles
},
(getRes) => {
var reply = "";
getRes.on("data", (chunk) => {
(reply += chunk);
}
);
getRes.on("end", () => {
var obj = "";
var obj = JSON.parse(reply);
gotCars(obj.Response.VehicleList);
}
);
}
);
function gotCars(carsJustObtained) {
// PASS ALL CARS
for (var car of carsJustObtained) {
// NOW FOR A GIVEN CAR...
for (var i=0; i<=1; i++){
// ...GET PRICES TYPES 2 AND 3
let path_get_all_prices = `/Gps/GetAllVehiclePrices?vehicleID=${car.ID}&securityToken=${req.body.secrets.securityToken}&vehiclePriceTypeID=${price_type[i]}`;
// console.log(`path_get_all_prices is ${path_get_all_prices}`);
// console.log(`i is ${i}`);
// console.log(`price_type[i] is ${price_type[i]}`);
let get = https.get(
{
hostname: 'api.kbb.com.br',
path: path_get_all_prices
},
(getRes) => {
var reply = "";
getRes.on("data", (chunk) => {
(reply += chunk);
}
);
getRes.on("end", () => {
var obj = "";
var obj = JSON.parse(reply);
// WRITE PRICES INTO OBJECT >> APPEND RESULTS
var grades_array = obj.Response;
storeCarData(car, grades_array,i);
});
}
);
// price_description[0] is equal to C2C
// price_description[1] is equal to C2B
// grades_array[0] corresponds to VehicleGrade=2, thus "Fair"
// grades_array[1] corresponds to VehicleGrade=3, thus "Good"
// grades_array[2] corresponds to VehicleGrade=2, thus "Excellent"
function storeCarData(car, grades_array,i){
var carData = {
timestamp: epoch,
ID : car.ID,
BrandID : car.BrandID,
ModelID : car.ModelID,
VersionID : car.VersionID,
BrandName : car.BrandName,
ModelName : car.ModelName,
VersionName : car.VersionName,
Year : car.Year,
MSRP : car.MSRP,
ISV : car.ISV,
TransportCost : car.TransportCost,
AdminCost : car.AdminCost,
RoadTax : car.RoadTax,
StartYearImported : car.StartYearImported,
EndYearImported : car.EndYearImported,
BodyTypeID : car.BodyTypeID,
CC : car.CC,
HP : car.HP,
KW : car.KW,
Torque : car.Torque,
NrCilinders : car.NrCilinders,
TransmissionTypeID : car.TransmissionTypeID,
Gears : car.Gears,
NrDoors : car.NrDoors,
NrSeats : car.NrSeats,
FuelTypeID : car.FuelTypeID,
EngineTypeID : car.EngineTypeID,
ExhaustTypeID : car.ExhaustTypeID,
DistanceAxis : car.DistanceAxis,
Volume : car.Volume,
DriveTrainID : car.DriveTrainID,
Weight : car.Weight,
WeightCarriage : car.WeightCarriage,
VehicleTypeID : car.VehicleTypeID,
VehicleCilindersID : car.VehicleCilindersID,
FirstMediaURL : car.FirstMediaURL,
CombinedConsumption : car.CombinedConsumption,
UrbanConsumption : car.UrbanConsumption,
ExtraUrbanConsumption : car.ExtraUrbanConsumption,
MaximumSpeed : car.MaximumSpeed,
FuelTankRangeKM : car.FuelTankRangeKM,
FuelTankCapacity : car.FuelTankCapacity,
Acceleration0to100 : car.Acceleration0to100,
EmissionsCO2 : car.EmissionsCO2,
FirstMktCategoryID : car.FirstMktCategoryID,
PriceType : car.PriceType,
Grade : car.Grade,
TAMileage : car.TAMileage,
TAAge : car.TAAge,
AVMileage : car.AVMileage,
AuctionPrice : car.AuctionPrice,
AskingPrice : car.AskingPrice,
TradeInPrice : car.TradeInPrice,
PrivatePartyPrice : car.PrivatePartyPrice,
C2CPrice : car.C2CPrice,
VersionCatalogID : car.VersionCatalogID,
ExternalReference : car.ExternalReference,
VehicleSeriesName : car.VehicleSeriesName,
VehicleVersionNameLong : car.VehicleVersionNameLong,
FirstMediaCompleteURLLarge : car.FirstMediaCompleteURLLarge,
FirstMediaCompleteURLMedium : car.FirstMediaCompleteURLMedium,
FirstMediaCompleteURLSmall : car.FirstMediaCompleteURLSmall,
BedLength : car.BedLength,
CabType : car.CabType,
}
storePrices(grades_array,carData.timestamp, carData.ID, price_description[i]);
};
function storePrices(grades_array, timestamp, carID, price_description){
var prices = {
timestamp:timestamp,
carID:carID,
[`PriceFPP_FairCondition_${price_description}`]: grades_array[0].VehiclePrices.FPP,
[`PriceFPP_GoodCondition_${price_description}`]: grades_array[1].VehiclePrices.FPP,
[`PriceFPP_ExcellentCondition_${price_description}`]: grades_array[2].VehiclePrices.FPP,
[`PriceLow_FairCondition_${price_description}`]: grades_array[0].VehiclePrices.PriceLow,
[`PriceLow_GoodCondition_${price_description}`]: grades_array[1].VehiclePrices.PriceLow,
[`PriceLow_ExcellentCondition_${price_description}`]: grades_array[2].VehiclePrices.PriceLow,
[`PriceHigh_FairCondition_${price_description}`]: grades_array[0].VehiclePrices.PriceHigh,
[`PriceHigh_GoodCondition_${price_description}`]: grades_array[1].VehiclePrices.PriceHigh,
[`PriceHigh_ExcellentCondition_${price_description}`]: grades_array[2].VehiclePrices.PriceHigh,
[`EquipmentAdjustedPrice_FairCondition_${price_description}`]: grades_array[0].VehiclePrices.EquipmentAdjustedPrice,
[`EquipmentAdjustedPrice_GoodCondition_${price_description}`]: grades_array[1].VehiclePrices.EquipmentAdjustedPrice,
[`EquipmentAdjustedPrice_ExcellentCondition_${price_description}`]: grades_array[2].VehiclePrices.EquipmentAdjustedPrice,
[`BaseDiscountedPrice_FairCondition_${price_description}`]: grades_array[0].VehiclePrices.BaseDiscountedPrice,
[`BaseDiscountedPrice_GoodCondition_${price_description}`]: grades_array[1].VehiclePrices.BaseDiscountedPrice,
[`BaseDiscountedPrice_ExcellentCondition_${price_description}`]: grades_array[2].VehiclePrices.BaseDiscountedPrice
}
};
};
};
};
}
Almost any time that you see a call to some .on(...), that code isn’t going to run right away, but at some future time.
To make this async, you can wrap the whole thing in a Promise() constructor, and then call resolve() when the thing that you were waiting for has actually happened.
The minimal change to your code for that would be:
let get = new Promise(resolve => {
https.get(
{
hostname: "api.kbb.com.br",
path: path_get_all_vehicles,
},
(getRes) => {
var reply = "";
getRes.on("data", (chunk) => {
reply += chunk;
});
getRes.on("end", () => {
var obj = "";
var obj = JSON.parse(reply);
gotCars(obj.Response.VehicleList);
resolve();
});
}
);
});
Now if you console.log() the value of get, you’ll see that it’s a Promise object. The for loop will still keep running, but now that you have a promise, you can await get on the next line. To be able to use that syntax though, you’ll have to make the containing function async, that is, change (req, res) => … to async (req, res) => ….
Some additional things to worry about later, once you get the basics working:
What if the http request fails? Then you’ll want to throw an exception or something. Look up Promise.reject() for that case.
Now that the request handler is async, express’s default error-handling stuff won’t work so well. This package might help though I personally haven’t used it.
Those are other issues for other times, but hopefully this helps you get unstuck.
Related
This problem is very annoying. So, I am making a scheduled trigger run every 24 hours. It simply gets items from one collection does some data processing then appends information to another collection. The functioning code works even when the function runs. But it will not let me save because there are "runtime" errors? Even though it was executed perfectly and returned.
Console Error
> result (JavaScript):
EJSON.parse('{"$undefined":true}')
I suppose this has something to do with returning. but when I return null I get this:
> result:
null
> result (JavaScript):
EJSON.parse('null')
when trying to save I get this at the top of the page:
runtime error during function validation
Function Code:
exports = async function() {
const usersCol = context.services.get("SchoologyDashCluster").db("SchoologyDashApp").collection("users");
const gradesCol = context.services.get("SchoologyDashCluster").db("SchoologyDashApp").collection("grades");
var usersCusor = await usersCol.find( ).toArray();
var gradesCusor = await gradesCol.find( ).toArray();
let insert = [];
for (let i = 0; i < usersCusor.length; i++) {
var user = usersCusor[i];
var userSavedGrades = gradesCusor[i].grades
var currentGrades = await getGrades(user.schoologyUID, user.consumerKey, user.secretKey);
var lastGraded = NaN;
let index = gradesCusor[i].grades.length - 1;
while (true) {
if (gradesCusor[i].grades[index].changed == 1) {
lastGraded = index;
break
}
index = index - 1;
}
console.log(lastGraded)
if (userSavedGrades[lastGraded].grades.ga == currentGrades.ga){
currentGrades = { changed : 0, time: new Date().getTime()};
} else {
currentGrades = {changed : 1, grades: currentGrades, time : new Date().getTime()};
}
gradesCol.updateOne(
{"user" : user._id},
{"$push" : {"grades" : currentGrades}}
)
}
// return usersCol.find( );
return null;
};
The answer was simple and now I feel ignorant. Instinctual I put the module imports at the top of the document. However this is incorrect and they need to be placed in the exports function, like so:
exports = function (x,y,z) {
const http = context.http;
return;
}
I have a fairly straightforward node script. It pulls in data from an API, loops through it all and compiles 2 reports. One in a JSON format, the other in CSV. It then, sorts the data and finally exports both a JSON file and then 2 CSV files (duplicates).
I have put logging into my script and found that the script fails to advance up to and past the part where I sort the arrays before exporting them into file formats.
What could be causing this?
** It's worth noting that if I remove the sorting and exporting at the bottom for either the JSON or the CSV, the script will run completely. It just wont do both at the same time. But stranger still is that it was working last week in this exact same configuration, but suddenly breaks.
const { xm, xm_env, uncaught_endpoint, prod_xm } = require('./../config');
const util = require('./../node_modules/xmtoolbox/lib/util');
const path = require('path');
const workflow = process.argv.slice(2);
const fs = require('fs')
const pathname = './../../../Logs/';
const filename = path.basename(__filename, '.js');
let fields = [
'sub_name',
'sub_uuid',
'sub_form_name',
'sub_form_uuid',
'workflow_name',
'workflow_uuid',
'owner_targetName',
'owner_uuid',
'owner_firstName',
'owner_lastName',
'recipient_targetName',
'recipient_uuid',
'recipient_firstName',
'recipient_lastName',
];
(async (env, workflow) => {
try {
const report = [];
const json = [];
const subscriptions = await xm.subscriptions.getMany(env);
util.buildLogger(pathname, filename).info('Pulled in all the subscriptions...');
await Promise.all(subscriptions.map(async sub => {
if (sub.criteria) {
let sub_obj = {
sub_name : sub.name,
sub_uuid : sub.id,
sub_form_name : sub.form.name,
sub_form_uuid : sub.form.id,
workflow_name : sub.form.plan.name,
workflow_uuid : sub.form.plan.id,
owner_targetName : sub.owner.targetName,
owner_uuid : sub.owner.id,
owner_firstName : sub.owner.firstName,
owner_lastName : sub.owner.lastName
};
let json_obj = {
recipients : [],
id : sub.id,
name : sub.name,
form : sub.form,
owner : sub.owner,
created : sub.created,
criteria : sub.criteria.data,
description : sub.description,
notificationDelay : sub.notificationDelay
};
report.push(sub_obj);
if (sub.recipients && sub.recipients.count > 1) {
let recipients = await xm.subscriptions.getSubscribers(env, '', sub.id);
await Promise.all(recipients.map(async r => {
json_obj.recipients.push({ targetName: r.targetName, id : r.id })
let recip_obj = {
sub_name : sub.name,
sub_uuid : sub.id,
sub_form_name : sub.form.name,
sub_form_uuid : sub.form.id,
workflow_name : sub.form.plan.name,
workflow_uuid : sub.form.plan.id,
owner_targetName : sub.owner.targetName,
owner_uuid : sub.owner.id,
owner_firstName : sub.owner.firstName,
owner_lastName : sub.owner.lastName,
recipient_targetName : r.targetName,
recipient_firstName : r.firstName,
recipient_lastName : r.lastName,
recipient_uuid : r.id
};
}
json.push(json_obj);
}
}));
util.buildLogger(pathname, filename).info('Looped through all subscriptions and built report arrays...');
// -------------- LOGGING NEVER MAKES IT PAST THIS PART ---------------------- //
let sorted_report = await util.awaitSort(report, ['form_name', 'sub_name']);
util.buildLogger(pathname, filename).info('Built Sorted Report...');
let sorted_json = await util.awaitSort(json, ['name']);
util.buildLogger(pathname, filename).info('Built sorted_json Report...');
fs.writeFileSync('./../../../Feeds/Subs_JSON.json', JSON.stringify(sorted_json))
util.buildCSV(fields, sorted_report, 'Subscriptions');
util.buildCSV(fields, sorted_report, `${util.today()}_Subscriptions`, true);
util.buildLogger(pathname, filename).info('All files written...');
} catch (e) {
util.uncaught_exception_webhook(xm_env, path.basename(__filename), workflow, uncaught_endpoint, e);
}
})(prod_xm, workflow);
Await Sort Function from UTIL file
async function awaitSort(data, keys) {
let sorted_data = data.sort((a, b) => {
keys.map(i => {
if (a[i].toLowerCase() < b[i].toLowerCase()) {
return -1
}
if (a[i].toLowerCase > b[i].toLowerCase()) {
return 1;
}
return 0;
});
});
return sorted_data;
}
I have a pretty simple firebase function :
exports.sendFollowNotification = functions.database.ref('PendingRequest/{receiver_id}/{sender_id}').onWrite(requestEvent => {
const requestSnapShot = requestEvent.data;
const senderId = requestEvent.params.sender_id;
const targetId = requestEvent.params.receiver_id;
const target_token = requestSnapShot.child('sender').val();
const sender_token = requestSnapShot.child('receiver').val();
console.log('sender_id :'+senderId);
console.log('target_id :'+targetId);
console.log('target_token: '+ target_token);
console.log('sender_token: '+sender_token);
const pendingRequestPayload = {
data: {
token_sender : sender_token,
token_target : target_token,
request_sender : senderId,
request_receiver : targetId,
my_message_id: '0'
}
};
if(target_token != null){
// Send a message to devices subscribed to the provided topic.
return admin.messaging().sendToDevice(target_token, pendingRequestPayload)
.then(function (response) {
// See the MessagingTopicResponse reference documentation for the
// contents of response.
console.log("Successfully sent message:", response);
})
.catch(function (error) {
console.log("Error sending message:", error);
});
}
Whenever this function fires there are two values that gets swapped : senderId gets targetId value and vice versa. Both values are retrieved with the params property while nothing strange happens to the values i'm getting from requestSnapShot.child('value_name').val();
The dumb solution is just to swap the two values whenever i need them but well, that's a really dumb solution. What am I missing here ?
If "target" is "receiver", these are swapped:
const target_token = requestSnapShot.child('sender').val();
const sender_token = requestSnapShot.child('receiver').val();
Are you doing that intentionally to work around the problem?
Update:
It's hard to guess why this isn't working for you. I copied your code, eliminated your work-around, and shortened it for testing:
exports.sendFollowNotification = functions.database.ref('PendingRequest/{receiver_id}/{sender_id}')
.onWrite(requestEvent => {
const requestSnapShot = requestEvent.data;
const senderId = requestEvent.params.sender_id;
const targetId = requestEvent.params.receiver_id;
const target_token = requestSnapShot.child('receiver').val();
const sender_token = requestSnapShot.child('sender').val();
console.log('sender_id :'+senderId);
console.log('target_id :'+targetId);
console.log('target_token: '+ target_token);
console.log('sender_token: '+sender_token);
});
Ran with this data:
{
"PendingRequest" : {
"R1" : {
"S1" : {
"receiver" : "R-token",
"sender" : "S-token"
}
}
}
}
And got this log output:
sender_token: S-token
target_token: R-token
target_id :R1
sender_id :S1
I have this code in my app.js for send chat and read chat in my application
$scope.messageshistory = {};
$scope.tmp = {};
// send message
$scope.sendMessage = function(){
$scope.messages = {
from : $scope.datauser['data']['_id'],
fromname : $scope.datauser['data']['nama'],
to : $scope.tmpuserid,
message : $scope.tmp['sendmessage'],
time : moment()
};
//event emit message
socket.emit('message',$scope.messages,function(callback){
if(!callback['error']){
$scope.messages['time'] = moment($scope.messages['time']).format('DD-MMMM-YYYY HH:MM');
if ($scope.messageshistory.hasOwnProperty($scope.tmpuserid)){ //yg di json yg paling awal
$scope.messageshistory[$scope.tmpuserid].push($scope.messages);
}else{
$scope.messageshistory[$scope.tmpuserid] = [];
$scope.messageshistory[$scope.tmpuserid].push($scope.messages);
}
$scope.tmp['sendmessage'] = '';
}else{
var msg = callback['error'];
navigator.notification.alert(msg,'','Error Report', 'Ok');
}
$scope.$apply();
});
};
//event read message
socket.on('message', function (data) {
window.plugin.notification.local.add({
id : moment(),
title : data['fromname'],
message : data['message'].substr(0,20) + ' ...',
led : 'A0FF05',
json : JSON.stringify({ routes:'chat', nama :data['fromname'],from:data['from'] })
});
data['time'] = moment(data['time']).format('DD-MMMM-YYYY HH:MM');
if ($scope.messageshistory.hasOwnProperty(data['from'])){
$scope.messageshistory[data['from']].push(data);
}else{
$scope.messageshistory[data['from']] = [];
$scope.messageshistory[data['from']].push(data);
}
for(var i = 0; i<= $scope.datauser['data']['friends'].length; i++){
if($scope.datauser['data']['friends'][i]['userid'] == data['from']){
$scope.datauser['data']['friends'][i]['ischat'] = true;
break;
}
};
$scope.$apply();
});
my question is how to take the last value in message property from $scope.messageshistory, because $scope.messages is for sending the message and $scope.messageshistory is to keep the chat history. This is the chat activity image:
just from this activity, $scope.messageshistory will save the data in it JSON as:
{
"5512": [{
"from": "561c",
"fromname": "ryan",
"to": "5512",
"message": "hey",
"time": "18-Maret-2016 21:03"
}, {
"from": "5512",
"fromname": "sasa",
"to": "561c",
"message": "hello",
"time": "18-Maret-2016 21:03",
"_id": "593s"
}]
}
I get this value from using angular.toJson($scope.messageshistory), and this array will always add up if the chat activities still going on. And my intention to get the last value in message property from $scope.messageshistoryis to use in Text-to-Speech feature in my application. This is the code:
$scope.speakText = function() {
TTS.speak({
text: **this the place for the code**,
locale: 'en-GB',
rate: 0.75
}, function () {
// handle the success case
}, function (reason) {
// Handle the error case
});
};
it will read the last message in $scope.messageshistory. So, what code that I must write to take the last value?
You have to do the following:
var msgs = $scope.messageshistory[$scope.tmpuserid]
var yourLastMessage = msgs[msgs.length-1].message
// you could also add a quick check so you don't get
// an error if the messages array is emtpy :
// var yourLastMessage = (msgs && msgs[msgs.length-1] ? msgs[msgs.length-1].message : null)
Edit
Some explanation per your comment :
var msgs = $scope.messageshistory[$scope.tmpuserid]
// msgs is now an Array containing Objects
// [{message : 'xxx'},{message : 'yyy'}]
// we take the last entry of the msgs Array (msgs.length-1)
// so msgs[msgs.length-1] is the last object ({message : 'yyy'})
// and finally we take the 'message' property' of that object:
var yourLastMessage = msgs[msgs.length-1].message
assuming that the keys in the history object are ascending numbers and taking into account that the order of keys in an object is not specified by W3C you will have to do the following:
get all keys
find the "latest" (hence the biggest number)
fetch it
so you could do for example
var keys = Object.keys($scope.messagehistory);
keys.sort (function (a, b) {
return a - b;
});
var result = keys[keys.length - 1];
I am making hubot-slack just for fun.
The thing I want to implement is let hubot get several questions at once. For instance, like below:
user : #hubot: add job
hubot : what is your job ?
user : jobless
hubot : where is your workplace ?
user : home
hubot : your job has registered.
As you can see, user will trigger event only once with command add job.
So following is my javascript source to handle it.
Calling part :
robot.respond(/register me/igm, function(msg){
//var promptConsole = new Prompt(msg);
var questions = [
{'question' : 'What is your name ?', 'dataname' : 'name'}
, {'question' : 'What is your job ?', 'dataname' : 'job'}
];
var promptConsole = new Prompt(msg, questions);
promptConsole.init().startPrompt(robot);
});
and Prompt Object :
var Prompt = function (msg, questions) {
console.log(msg);
this.msg = msg;
this.user = msg.message.user;
this.room = msg.message.room;
this.results;
this.questions = questions;
//console.log(user +' is in room [ ' + room + ' ] ');
this.init = function () {
this.results = [];
for(var i = 0; i < questions.length; i ++) {
var singleQuestion = questions[i];
var result = {'key': '', 'question' : '', 'value' : ''};
result.key = singleQuestion.dataname;
result.question = singleQuestion.question;
this.results.push(result);
}
return this;
}
this.startPrompt = function () {
for(var i = 0; i < this.results.length; i ++) {
console.log(this.results[i]);
this.msg.send(this.results[i].question);
}
}
}
However, this function can not be implemented with robot.hear or robot.respond, since user input happens only once.
Is there any other way to implement this? If not, I should separate this function to many part like add job, add workplace and so on.