I have a method that is failing when returning an array of objects. As mentioned in the title - the array is confirmed to be populated but is empty in the response.
Here is the full flow:
The Url:
http://localhost:53000/api/v1/landmarks?lat=40.76959&lng=-73.95136&radius=160
Is routed to the corresponding index:
api.route('/api/v1/landmarks').get(Landmark.list);
The Index Calls a Service:
exports.list = (req, res) => {
const landmark = new LandmarkService();
landmark.getLandmarks(req)
.then(landmarks => {
var response = new Object();
response.startindex = req.query.page;
response.limit = req.query.per_page;
response.landmarks = landmarks;
res.json(response);
})
.catch(err => {
logger.error(err);
res.status(422).send(err.errors);
});
};
The Service Method Uses a Data Access Class to Return the Promise
getLandmarks(req) {
const params = req.params || {};
const query = req.query || {};
const page = parseInt(query.page, 10) || 1;
const perPage = parseInt(query.per_page, 10);
const userLatitude = parseFloat(query.lat);
const userLongitude = parseFloat(query.lng);
const userRadius = parseFloat(query.radius) || 10;
const utils = new Utils();
const data = new DataService();
const landmarkProperties = ['key','building','street','category','closing',
'email','name','opening','phone','postal','timestamp','type','web'];
return data.db_GetAllByLocation(landmarksRef, landmarkLocationsRef,
landmarkProperties, userLatitude, userLongitude, userRadius);
} // getLandmarks
However, the response is always empty.
I am building an array in the called method and populating it with JSON objects. That is what is supposed to be sent back in the response. I can confirm that the attributes array is correctly populated before I hit the return statement. I can log it to the console. I can also send back a test array filled with stub values successfully.
I have a feeling it is how I am setting things up inside the Promise?
Data Access Method That Should Return Array of Objects:
db_GetAllByLocation(ref, ref_locations, properties, user_latitude, user_longitude, user_radius)
{
const landmarkGeoFire = new GeoFire(ref_locations);
var geoQuery = landmarkGeoFire.query({
center: [user_latitude, user_longitude],
radius: user_radius
});
var locations = [];
var onKeyEnteredRegistration = geoQuery.on("key_entered", function (key, coordinates, distance) {
var location = {};
location.key = key;
location.latitude = coordinates[0];
location.longitude = coordinates[1];
location.distance = distance;
locations.push(location);
});
var attributes = [];
var onReadyRegistration = geoQuery.on("ready", function() {
ref.on('value', function (refsSnap) {
refsSnap.forEach((refSnap) => {
var list = refSnap;
locations.forEach(function(locationSnap)
{
//console.log(refSnap.key, '==', locationSnap.key);
if (refSnap.key == locationSnap.key)
{
var attribute = {};
for(var i=0; i<=properties.length-1; i++)
{
if(properties[i] == 'key') {
attribute[properties[i]] = refSnap.key;
continue;
}
attribute[properties[i]] = list.child(properties[i]).val();
}
attribute['latitude'] = locationSnap.latitude;
attribute['longitude'] = locationSnap.longitude;
attribute['distance'] = locationSnap.distance;
attributes.push(attribute);
} // refSnap.key == locationSnap.key
}); // locations.forEach
}); // refsSnap.forEach
return Promise.resolve(attributes); <-- does not resolve (throws 'cannot read property .then')
//geoQuery.cancel();
}); // ref.on
}); // onreadyregistration
return Promise.resolve(attributes); <-- comes back empty
}
It seems that data.db_GetAllByLocation is an asynchronous function, therefore the call resolve(landmarks); is getting called before the execution of the async function is finished. If the data.db_GetAllByLocation returns a promise then call the resolve(landmarks) inside the promise.
data.db_GetAllByLocation().then(function() {
resolve();
})
Also try the following modified db_GetAllByLocation()
db_GetAllByLocation(ref, ref_locations, properties, user_latitude, user_longitude, user_radius)
{
return new Promise(function(resolve, reject){
const landmarkGeoFire = new GeoFire(ref_locations);
var geoQuery = landmarkGeoFire.query({
center: [user_latitude, user_longitude],
radius: user_radius
});
var locations = [{}];
var onKeyEnteredRegistration = geoQuery.on("key_entered", function (key, coordinates, distance) {
var location = {};
location.key = key;
location.latitude = coordinates[0];
location.longitude = coordinates[1];
location.distance = distance;
locations.push(location);
});
var attributes = [{}];
var onReadyRegistration = geoQuery.on("ready", function() {
ref.on('value', function (refsSnap) {
refsSnap.forEach((refSnap) => {
var list = refSnap;
locations.forEach(function(locationSnap)
{
if (refSnap.key == locationSnap.key)
{
var attribute = {};
for(var i=0; i<=properties.length-1; i++)
{
if(properties[i] == 'key') {
attribute[properties[i]] = refSnap.key;
continue;
}
attribute[properties[i]] = list.child(properties[i]).val();
}
attribute['latitude'] = locationSnap.latitude;
attribute['longitude'] = locationSnap.longitude;
attribute['distance'] = locationSnap.distance;
attributes.push(attribute);
} // refSnap.key == locationSnap.key
}); // locations.forEach
}); // refsSnap.forEach
// return JSON.stringify(attributes);
return resolve(attributes);
}); // ref.on
}); // onreadyregistration
});
}
OK, I sorted this by removing all my code and writing some test logic (I should have done this before I posted my question).
The below flow works for me, and, applied back to my code, gave me the results I was looking for. No need to re-post the code, but maybe the below flow will be helpful to somebody.
route
api.route('/api/v1/landmarks').get(Landmark.test);
index
exports.test = (req, res) => {
const landmark = new LandmarkService();
landmark.getLandmarksTest(req)
.then(landmarks => {
var final = {};
final.attr1 = 'attr1';
final.attr2 = 'attr2';
final.landmarks = landmarks;
res.json(final);
})
.catch(err => {
logger.error(err);
res.status(422).send(err.errors);
});
};
service method
getLandmarksTest(req)
{
const data = new DataService();
data.db_PromiseTest().then(results => {
return Promise.resolve(results);
}).catch(err => {
return Promise.reject(err.errors);
});
}
data layer method
db_PromiseTest()
{
var stub = {
"name": "Madame Uppercut",
"age": 39,
"secretIdentity": "Jane Wilson",
"powers": [
"Million tonne punch",
"Damage resistance",
"Superhuman reflexes"
]
};
return Promise.resolve(stub);
}
Related
I'm trying to create a chrome extension, but I am having some trouble updating my DB.
In the code below I am using index.get to the the object that contains a certain value. If such an object doesn't exist I will create a new one, which works just fine.
But if the DB contains an object with the specified value, I want to append a new object to an array (allMessages) that is inside the object I searched for. The details doesn't really matter in this case.
What is important is to find out if the way I'm adding this new obj to the array (allMessages) is a valid way of updating the database.
records.forEach((person) => {
console.log("here1");
const index = objectStore.index("urlKeyValue");
let search = index.get(person.urlKeyValue);
search.onsuccess = function (event) {
if (search.result === undefined) {
// no record with that key
let request = objectStore.add(person);
request.onsuccess = function () {
console.log("Added: ", person);
};
} else {
// here I'm iterating an array that is inside the obj I searched for,
// and then checking if the key for that array matches **theUserId**
for (userObj of event.target.result.allMessages) {
if (theUserId == Object.keys(userObj)) {
// is this part correct. Is it possible to update the DB this way?
let objToAdd1 = {
time: person.allMessages[0][theUserId][0].time,
msg: person.allMessages[0][theUserId][0].msg,
};
let currentObj = userObj[theUserId];
let updatedObj = currentObj.push(objToAdd1);
}
}
)}
Using objectStore.openCursor you can update only part of the record.
The following updates only book prices.
const transaction = db.transaction("books", "readwrite");
const objectStore = transaction.objectStore("books");
records = [{ id: "kimetu", price: 600 }];
records.forEach((book) => {
const index = objectStore.index("id");
const search = index.get(book.id);
search.onsuccess = () => {
if (search.result === undefined) {
const request = objectStore.add(book);
request.onsuccess = () => {
console.log("Added: ", book);
};
} else {
const request = objectStore.openCursor(IDBKeyRange.only(book.id));
request.onsuccess = () => {
const cursor = request.result;
if (cursor) {
cursor.value.price = 1000;
const updateRequest = cursor.update(cursor.value);
updateRequest.onsuccess = () => {
console.log("Updated: ", cursor.value.price);
};
cursor.continue();
}
};
}
}
});
I am doing some practice in node.js. In this exercise I been asked to find a country name through a GET Http Request to an endpoint passing a page integer as a parameter.
Where the important response structs are these {page, total_pages, data}.
page is the current page,
total_pages is the last page,
data is an array of 10 country object.
In getCountryName func I am able to retrieve the right answer only if the answer is on the 1st page, the 1 iteration of the loop. So, why the loop only happens once?
Aditional, I wanted to retrieve the total_pages to replace the hardcode '25' value but I do not figure it out how to return it along with the search.
Any hint you wanna give me? The whole problem is in getCountryCode func.
'use strict';
const { Console } = require('console');
const https = require('https');
function makeRequest(page){
return new Promise(resolve => {
let obj='';
https.get('https://jsonmock.hackerrank.com/api/countries?page='+page, res => {
let data ='';
res.on('data',function(chunk){
data+=chunk;
});
res.on('end',function(){
obj=JSON.parse(data);
resolve(obj);
});
});
});
}
async function getCountryName(code) {
var res = '';
var pages = 25;
var i = 1;
while(i <= pages && res == ''){
console.log(i);
res = makeRequest(i)
.then(data => {
let f = ''
let p = data['total_pages'];
let search = data['data'].find(o => o.alpha3Code === code);
f = search != null ? search['name'] : f;
return f;
});
i++;
}
return res;
}
async function main() {
const name = await getCountryName('ARG');
console.log(`${name}\n`);
}
main();
Without modifying your code too much, this is how you do it:
'use strict';
const { Console } = require('console');
const https = require('https');
function makeRequest(page){
return new Promise(resolve => {
let obj='';
https.get('https://jsonmock.hackerrank.com/api/countries?page='+page, res => {
let data ='';
res.on('data',function(chunk){
data+=chunk;
});
res.on('end',function(){
obj=JSON.parse(data);
resolve(obj);
});
});
});
}
async function getCountryName(code) {
const pages = 25;
var i = 1;
let f = null
while(i <= pages && f === null){
console.log(i);
const data = await makeRequest(i) // put in try/catch
const p = data['total_pages'];
const search = data['data'].find(o => o.alpha3Code === code);
f = search !== null ? search['name'] : null;
i++;
}
return res;
}
async function main() {
const name = await getCountryName('ARG');
console.log(`${name}\n`);
}
main();
I am trying to read some data from 2 different tables and parse a CSV file before rendering an ejs file.
I can get the data from both tables and from the CSV file but I seem to be unable to return the result.
Pretty sure this is a problem with the way I handle async execution but I fail to see what I am doing wrong.
I've spent the last 2 days reading about this (including the threads around here) and browsing but somehow the answer still escapes me.
First file - usercms.js
app.get('/userscms', function(req, res)
{
existingUsers.getExistingUsers()
.then(function(appUsers)
{
//global users array
//I can display these in my ejs file
globalAppUsers = appUsers;
})
.then(existingUsersAttributesQlik.getExistingUsersAttributesQlik())
.then(function(usersQlikAttributes)
{
//global user attributes array
//undefined data
globalUsersQlikAttributes = usersQlikAttributes;
})
.then(existingSuppliers.parseSuppliersCSV())
.then(function(supplierData)
{
//the result I am expecting
//this prints undefined
console.log(supplierData);
}).then(function()
{
res.render('userscms.ejs',
{
users: globalAppUsers,
attributes: globalUsersQlikAttributes
});
});
});
Second function - getxistingUsers.js (identical to the getExistingUsersAttributesQlik, except for the query)
var userData = [];
var appUsers = [];
(function (exports)
{
exports.getExistingUsers = function ()
{
return promisemysql.createConnection(dbconfig.development).then(function(conn)
{
var result = conn.query("SELECT id, username, firstName, lastName, email, phone, lastLogin, isAdmin, isValid, isPhoneValid, accountCreationDateTime FROM Users");
conn.end();
return result;
}).then(function(rows)
{
return rows;
}).then(function(rows)
{
if (rows.length)
{
userData = [];
appUsers = [];
rows.forEach(function (elem)
{
userData.push(_.toArray(elem));
});
for (i = 0; i < userData.length; i++)
{
var appUser = new appUserModel.AppUser(
userData[i][0],
userData[i][1],
userData[i][2],
userData[i][3],
userData[i][4],
userData[i][5],
userData[i][6],
userData[i][7],
userData[i][8],
userData[i][9],
userData[i][10]);
appUsers.push(_.toArray(appUser));
}
return appUsers;
}
else
{
console.log("NOPE");
return null;
}
}).then(function(appUsers)
{
console.log(appUsers);
return appUsers;
});
};
})(typeof exports === 'undefined' ? this['getExistingUsers'] = {} : exports);
Third file - parseSuppliersCSV.js
var supplierData = [];
var suppliersData = [];
var csvCount = 0;
(function (exports)
{
exports.parseSuppliersCSV = function ()
{
return new Promise(function(resolve, reject)
{
var fileStream = fs.createReadStream("myCSV.csv");
var parser = fastCsv();
csvCount = 0;
supplierData = [];
suppliersData = [];
fileStream
.on("readable", function ()
{
var data;
while ((data = fileStream.read()) !== null)
{
parser.write(data);
}
})
.on("end", function ()
{
parser.end();
});
parser
.on("readable", function ()
{
var data;
while ((data = parser.read()) !== null)
{
if(csvCount >= 1)
{
csvCount++;
var arrayOfStrings = data[0].split(';');
var supplier = new supplierModel.Supplier(arrayOfStrings[0],arrayOfStrings[1]);
suppliersData.push(_.toArray(supplier));
}
else
{
csvCount++;
}
}
})
.on("end", function ()
{
console.log("done");
//all OK here
console.log(suppliersData);
//this doesn't seem to return anything
return suppliersData;
});
});
};
})(typeof exports === 'undefined' ? this['parseSuppliersCSV'] = {} : exports);
Any ideas what I am doing wrong? Am I approaching this the wrong way?
I'll take a guess here and assume the promise you created should resolve to something...instead of returning a value.
.on("end", function ()
{
console.log("done");
//all OK here
console.log(suppliersData);
//this doesn't seem to return anything
return resolve(suppliersData);
});
I have one function getUsers where I have one array jsonResponse. I am passing that array to computeData function. I want computeData function should be able to add items in jsonResponse array itself.
But below code is not adding in same array, as it always return empty array in response.send function.
index.js
exports.getUsers = functions.https.onRequest((request, response) => {
var x = [];
var xLocation,
yLocation = [],
jsonResponse = [];
// print algorithm name
console.log(request.query.algo);
let geoFire = new GeoFire(db.ref("/users/" + request.query.userId));
geoFire.get("location").then(function(location) {
xLocation = location;
});
db
.ref("/users/" + request.query.userId)
.once("value")
.then(function(snapshot) {
var jsonObject = snapshot.val();
var basicProfileJsonObject = jsonObject.basicProfile;
for (var key in basicProfileJsonObject) {
if (utils.isNumber(basicProfileJsonObject[key])) {
x.push(basicProfileJsonObject[key]);
}
}
db.ref("/users/").once("value").then(function(snapshot) {
var y = [];
snapshot.forEach(function(item) {
var user = item.val();
let userId = user.basicProfile.userId;
if (userId !== request.query.userId) {
if (xLocation == null) {
computeData(x, user, request.query.algo, jsonResponse);
} else {
let geoFire = new GeoFire(db.ref("/users/" + userId));
geoFire.get("location").then(function(location) {
if (location === null) {
console.log(
"Provided key is not in GeoFire, will ignore profile"
);
computeData(x, user, request.query.algo, jsonResponse);
} else {
console.log("Provided key has a location of " + location);
var distance = GeoFire.distance(xLocation, location); // in km
console.log("Distance: " + distance);
if (distance < 15) {
computeData(x, user, request.query.algo, jsonResponse);
}
}
});
}
}
});
response.send(jsonResponse);
});
});
});
function computeData(x, user, algo, jsonResponse) {
var similarityCount,
y = [];
var basicProfileJsonObject = user.basicProfile;
for (var key in basicProfileJsonObject) {
if (utils.isNumber(basicProfileJsonObject[key])) {
y.push(basicProfileJsonObject[key]);
}
}
if (algo === "cosine") {
// compute cosine value
similarityCount = cosineUtils.cosineSimilarity(x, y);
} else if (algo == "euclidean") {
// compute euclidean distance value
similarityCount = 1 / (1 + euclidean(x, y));
} else if (algo === "pearson-correlation") {
// compute pearson correlation coefficents
similarityCount = pcorr.pearsonCorrelation(x, y);
}
console.log(x);
console.log(y);
console.log(similarityCount);
jsonResponse.push(user);
}
Does anyone know how to pass array as reference and add items into it in Cloud Function for Firebase ?
Your else statement is a promise which means your loop would have finished and called response.send(jsonResponse); by the time it gets to computeData() in your else statement.
Try something like this, it doesn't touch all your variables but the main idea is to use Promise.all with computed values as resolved -
exports.getUsers = functions.https.onRequest((request, response) => {
// blah blah
var y = []; // store promises that resolves your computed value
snapshot.forEach(function(item) {
// blah blah
if (xLocation == null) {
y.push(Promise.resolve(computeData());
} else {
y.push(computeAnotherData(userId));
}
});
Promise.all(y)
.then(values => {
response.send(values);
});
});
function computeAnotherData(userId) {
let geoFire = new GeoFire(db.ref("/users/" + userId));
return geoFire.get("location").then(function(location) {
return computeData();
});
}
Hope it makes sense.
Hi I am using phonegap to develop a shopping app. I want to give the user an option to save their order and complete wheneven he/she finds convenient. My question where do I save the order data. Local file system or local db of the mobile device? I will like to save the order
in json format in a local file. Please suggest the best option for me. Also a snippet will be highly appreciated. Thanks
You could also use HTML5 localStorage as an easier alternative to file storage. I have been using an encapsulated version of localStorage to facilitate get/set operations and decrease namespace pollution. Please see code base below:
/**
* The class is designed to facilitate flexible permanent storage of key value
* pairs utilzing HTML5 localStorage.
*
* #class LocalMap
* #author Zorayr Khalapyan
* #version 10/25/2012
*/
var LocalMap = function ( name ) {
var that = {};
//Prevent compatability issues in different execution environments.
if ( !localStorage ) {
localStorage = {};
}
if ( !localStorage[name] ) {
localStorage[name] = "{}";
}
var setMap = function ( map ) {
localStorage[name] = JSON.stringify( map );
};
that.getMap = function () {
return JSON.parse( localStorage[name] );
};
/**
* Stores the specified (key, value) pair in the localStorage
* under the map's namespace.
*/
that.set = function ( name, object ) {
var map = that.getMap();
map[ name ] = object;
setMap( map );
};
that.get = function ( name ) {
var map = that.getMap();
return typeof( map[ name ] ) !== "undefined" ? map[name] : null;
};
that.importMap = function ( object ) {
var map = that.getMap();
var key;
for ( key in object ) {
if (object.hasOwnProperty(key)) {
map[key] = object[key];
}
}
setMap(map);
};
that.length = function () {
var map = that.getMap();
var size = 0, key;
for (key in map) {
if (map.hasOwnProperty(key)) size++;
}
return size;
};
that.erase = function () {
localStorage[name] = JSON.stringify({});
};
that.isSet = function (name) {
return that.get(name) != null;
};
that.release = function (name) {
var map = that.getMap();
if (map[name]) {
delete map[name];
}
setMap(map);
};
that.deleteNamespace = function(){
if (localStorage.hasOwnProperty(name)) {
delete localStorage[name];
}
};
return that;
};
LocalMap.destroy = function () {
for ( var item in localStorage ) {
if ( localStorage.hasOwnProperty( item ) ) {
delete localStorage[ item ];
}
}
};
LocalMap.exists = function (name) {
return (localStorage[name]) ? true : false;
};
Below are the unit tests for get and set functions:
test( "Test set()", function() {
var map = LocalMap('test-namespace');
///
map.set("var-1", "val-1");
map.set("var-2", "val-2");
map.set("var-3", "val-3");
//
ok(map.isSet("var-1"), "A variable should be successful set.");
ok(map.isSet("var-2"), "A variable should be successful set.");
ok(map.isSet("var-3"), "A variable should be successful set.");
});
test( "Test get()", function() {
var map = LocalMap('test-namespace');
map.set("var-1", "val-1");
map.set("var-2", "val-2");
map.set("var-3", "val-3");
///
var var1 = map.get("var-1");
var var2 = map.get("var-2");
var var3 = map.get("var-3");
var var4 = map.get("var-4");
//
equal(var1, "val-1", "A set variable should be succesfully retreived.");
equal(var2, "val-2", "A set variable should be succesfully retreived.");
equal(var3, "val-3", "A set variable should be succesfully retreived.");
equal(var4, null, "A variable that was not set should not be retreived.");
});
Hope this helps, and let me know if you have any questions.
How about the below code? I copied it from here. Actually I like its code.
// define dbContext & entities------------------------------------
var DemoDataContext = function () {
nova.data.DbContext.call(this, "Demo", "1.0", "Demo DB", 1000000);
this.users = new nova.data.Repository(this, User, "users");
this.roles = new nova.data.Repository(this, Role, "roles");
};
DemoDataContext.prototype = new nova.data.DbContext();
DemoDataContext.constructor = DemoDataContext;
var User = function () {
nova.data.Entity.call(this);
this.name = "";
this.password = "";
this.birthYear = 1980;
this.createdDate = new Date();
this.deleted = false;
};
User.prototype = new nova.data.Entity();
User.constructor = User;
var Role = function () {
nova.data.Entity.call(this);
this.name = "";
this.createdDate = new Date();
};
Role.prototype = new nova.data.Entity();
Role.constructor = Role;
// end define dbContext & entities------------------------------------
// service methods----------------------------------------------------
function getAllUsers(callback) {
new DemoDataContext().users.toArray(function (users) {
alert(users.length);
callback(users);
});
}
function getUserByName(name, callback) {
new DemoDataContext().users.where("name='" + name + "'").toArray(function (users) {
callback(users.firstOrDefault());
});
}
function addRole(roleName, callback) {
var role = new Role();
role.name = roleName;
var db = new DemoDataContext();
db.roles.add(role);
db.saveChanges(callback);
}
function updateUserPassword(username, password, callback) {
getUserByName(username, function (user) {
if (user == null) {
throw "no user found.";
}
user.password = password;
var db = new DemoDataContext();
db.users.update(user);
db.saveChanges(callback);
});
}
function deleteUserByName(name, callback) {
getUserByName(name, function (user) {
if (user == null) {
throw "no user found.";
}
var db = new DemoDataContext();
db.users.remove(user);
db.saveChanges(callback);
});
}
// end service methods----------------------------------------------------