Updating object property with value of Promise - javascript

I using Express with Node to interact with a MySQL database.
The create method of my Card object will save the data to the database and should update the object's id to be the insertId returned from the query.
class Card {
constructor(d) {
this.id = d.id || 0;
this.playerId = d.playerId;
this.drawn = false;
this.title = d.title
this.qty = d.qty;
this.hint = d.hint;
this.type = d.type;
this.descr = d.descr;
this.pair = d.pair
this.data = d;
}
create(conn) {
return new Promise( (resolve, reject) => {
var query = "INSERT INTO cards SET ? ";
conn.query(query, this.data, function(err, result) {
if (err) throw err;
(typeof result.insertId === 'number')
? resolve(result.insertId)
: reject("Failed to insert a new Card.");
resolve(result.insertId);
})
}).then( (id) => {
this.id = id;
console.log("Your insert id is");
console.log(this.id) // GREAT! It prints the correct id
return this; // Set the value of the promise to this Card instance
})
}
}
After creating a card, it should print the json representation of the card.
router.post('/create-card', function(req, res) {
var data = req.body;
var card = new Card(data);
card = card.create(db.connection);
res.json(card);
})
But the id of the returned object is always the default value (0).
I have played with a few different ways of trying to capture the id, set the object's id property with the new value, and persist that value to the object. However, it always seems to be a more locally scoped version of the card that gets updated, not the original card (which is printed to the page).
When using Promises, what is the proper place to assign object data and return that instance of the object?

You're calling res.json too soon. You need to await the promise's resolution:
card.create(db.connection).then( _ => res.json(card));

You are doing an async operation in create method but dont use it properly. You have to do one of two things:
Return promise from create, then wait for it to fulfilled with 'then' in your post route.
Use await and wait for the promise to be resolved.
In your code for now you are running in your router synchronize code while your create operation is async...

Related

Return cached array instead of Realtime Database array

I have 3 separate js files, one js file has function that i export and access on the other two js file. The purpose of the function is to get data from realtime database firebase once, when the function is called for the first time it will get data from database and store it on array outside the function. Then on second call, it will just return the array that contains data from database.
let mySub = [];
export function getMySub(id) { // get users subjects
if (mySub.length !== 0) {
console.log('From Array SUBJECTS');
return mySub;
} else if (mySub.length == 0) {
get(child(path, "General")).then((snapshot) => {
snapshot.forEach(function(childSnapshot) {
var data = childSnapshot.val();
if (data.INS_SYS_ID == id) {
mySub.push([data.COURSE_NAME, data.SECTION, childSnapshot.key, data.ENROLLED_STUDENTS]);
}
})
}).catch((error) => {
console.error(error);
});
console.log('From DB SUBJECTS');
return mySub;
}
}
It works like this, if the mySub array length is not equal to zero the function will just return the array. if the array length is equal to zero it will get data from database and store it to mySub.
I also have a function on that same js file that updates the mySub array when i need to.
export function updateDisplayedSub(coursename, section, key, enrolledStudents) {
mySub.push([coursename, section, key, enrolledStudents]);
}
Now here is my problem, whenever i call the function that updates mySub array and call the function getMySub(id). it returns array that reads from database instead of returning the existing array that i updated.
updateDisplayedSub and getMySub is called on different js file, does this affect? I'm using this function to avoid too many reads and conserve bandwidth on my free tier hosting firebase.
It seems that you are trying to invent memoization.
Your current method falls short because if you vary the id parameter, there's no way of telling if the cached data in mySub refers to that id or not. This will likely cause bugs.
Instead, we can write a higher order function that returns a function that wraps your existing function:
const memoizeUnary = (f) => {
const map = new Map();
return (v, skipCached) => {
if (!skipCached && map.has(v)) {
return map.get(v);
}
else {
const r = f(v);
map.set(v, r);
return r;
}
};
};
and use it to wrap another function:
function foo(v) {
console.log("foo invoked");
return `hello ${v}`;
}
const memoizedFoo = memoizeUnary(foo);
Now, if we call
memoizedFoo("monkey")
for the first time, the function that it wraps will be invoked, its return value stored in a map and then returned to the caller.
The second time we call
memoizedFoo("monkey")
the function it wraps will not be called, and the cached return value for parameter "monkey" will be retrieved and returned to the caller.
If you want to reset the cache and fetch from the wrapped function again, you can add an optional parameter, true to skip the cache, re-fetch and store the new value in the cache:
memoizedFoo("monkey", true)
Pop open the snippet below to see this in action:
const memoizeUnary = (f) => {
const map = new Map();
return (v, skipCached) => {
if (!skipCached && map.has(v)) {
return map.get(v);
} else {
const r = f(v);
map.set(v, r);
return r;
}
};
};
function foo(v) {
console.log("foo invoked");
return `hello ${v}`;
}
const memoizedFoo = memoizeUnary(foo);
memoizedFoo("a");
memoizedFoo("a");
memoizedFoo("a");
memoizedFoo("a");
memoizedFoo("a");
memoizedFoo("a", true);
memoizedFoo("a");
memoizedFoo("a");
memoizedFoo("a");
Notice how, even though we call memoizedFoo("a") multiple times, the function it wraps is only called for the first time or if we supply a second true parameter.

Running 2 async methods in sequence. 2nd method dependent on 1st method

I'm new with Typescript/JS and I'm coming from Java.
I am trying to solve a scenario wherein the 2nd method call is dependent on 1st method's return value.
I managed to create a getCustomerNumber() method that is able to return a string value instead of undefined The 2nd method getCustomerName() needs the return value of getCustomerNumber() as argument for it to successfully return the customer name value.
I tried making both of them as async methods that returns a Promise<string> object. However, getCustomerName() fails to retrieve a customer number from getCustomerNumber()
private async getCustomerNumber(rorn : string): Promise<string> {
const getCustNo = new Request();
getCustNo.transaction = "GetCustNum";
getCustNo.argument = rorn;
let customerNo;
await Service.execute(getCustNo).then((response: IResponse) => {
customerNo = response.items[0]["CustNum"];
}).catch((response: IResponse) => {
this.log.Error(response.errorMessage);
});
return customerNo;
}
private async getCustomerName(cuno: string) : Promise<string> {
const getCustName = new Request();
getCustName.transaction = "GetCustName";
getCustName.argument = cuno;
let customerName;
await Service.execute(getCustName).then((response: IResponse) => {
customerName = response.items[0]["CustName"];
}).catch((response: IResponse) => {
this.log.Error(response.errorMessage);
});
return customerName;
}
private async callMethodsAndSetLabelText() : Promise <void> {
const orderNum = "123456";
const customerNumber = await this.getCustomerNumber(orderNum).trim());
const customerName = await this.getCustomerName(customerNumber) ; //get customer name by number
console.log(customerName);
const labelElement = new LabelElement();
labelElement.setText(customerName);
}
Calling the methods:
callMethodsAndSetLabelText()
Logging the customer name in callMethodsAndSetLabelText() results to undefined
console.log(customerName); //undefined
I don't know how to set it up in a way that will work for the 2nd method since it's dependent on the 1st method.
I'd appreciate any comments or suggestions.
Thank you

Creating my own sql wrapper for nodejs/express

I'm trying to create my own wrapper for mysql for my nodejs application. I have two questions here one of which theres a work around and one where I'm unsure what to do as my javascript skills are still in the learning phase.
First thing: As of right now when you navigate to /api/finance it directs you to the finance controller and the index method. This is currently just for testing purposes trying to figure out how to this kind of stuff.
FinanceController:
const sql = require('../../sql.js')
module.exports = {
index: (req, res, next) => {
sql.get('test').then((result) => {
res.status(200).json(result);
})
}
}
sql.js
const mysql = require('mysql');
const { DB } = require('./config')
var connection = mysql.createConnection(DB)
module.exports = {
get: function(table, columns = '*') {
return new Promise((resolve, reject) => {
connection.query('SELECT ?? FROM ?? ', [columns, table], function (error, results, fields) {
if (error) throw error;
resolve(results);
});
})
},
all: function(table) {
return new Promise((resolve, reject) => {
connection.query('SELECT * FROM ?? ', table, function (error, results, fields) {
if (error) throw error;
resolve(results);
});
})
},
where: function(){
console.log('where called')
}
}
As you can see, I have a get() and all(). get() allows you to pass the table name and an array of columns for example: ['id', 'name'] would get you the id column and name column. columns = '*' was an attempt on being able to use one function to either get all columns of the table or specify specific columns however it returns an error: Unknown column in 'field list' so all() was my "workaround" however i'd like it to be one function.
Next I can't figure out how to stack/pipe methods? if thats the word.
The goal here would be so I could call the function like this:
index: (req, res, next) => {
sql.all('test').where().then((result) => {
res.status(200).json(result);
})
}
}
obviously within the .where() I would have it like: .where('id', '=', 'userID') or something along those lines.
however I'm unsure on how to go about doing that and would like some guidance if its possible. I receive the error: sql.all(...).where is not a function
Instead of immediately launching the SQL, you should simply register the provided information in an instance object (having the same methods) and return that object, and let each method enrich the SQL until the end of the chain is reached and you call a method that will launch the SQL.
The object that is passed from one method to the next (as this) maintains state, and collects the different elements of the SQL statement.
Here is one way to do it.
NB: In this demo I used a mock-object for connection. This mock object will not actually connect to a database. Its query method will just produce the final SQL (with all ? resolved) instead of a real data set.
// Mock connection object for this snippet only. Just produces the final SQL, not the result
var connection = {
query(sql, params, callback) {
let i = 0;
sql = sql.replace(/\?\??/g, (m) => {
if (m.length > 1) return [].concat(params[i++]).map(p => "`" + p + "`").join(", ");
if (typeof params[i] === "string") return "'" + params[i++].replace(/'/g, "''") + "'";
return params[i++];
});
setTimeout(callback(null, sql));
}
}
// Function to create an instance when a first method call is made on the `sql` object directly
// Chained method calls will keep using that instance
function getInstance(inst) {
if (inst.columns) return inst; // Keep using same instance in the chain
inst = Object.create(inst); // No instance yet: create one
inst.table = null;
inst.params = [];
inst.columns = [];
inst.conditions = [];
inst.order = [];
return inst;
}
// This sql object serves a starting point as well
// as proto object for the instance object that
// passes through the chain:
var sql = {
from(table) {
let inst = getInstance(this);
inst.table = table;
return inst;
},
select(...columns) {
let inst = getInstance(this);
inst.columns = inst.columns.concat(columns);
return inst;
},
where(column, cmp, value) {
if (!["<",">","<=",">=","=","!="].includes(cmp)) throw "invalid operator";
let inst = getInstance(this);
inst.params.push(column, value);
inst.conditions.push(cmp);
return inst;
},
orderBy(...columns) {
let inst = getInstance(this);
inst.order = inst.order.concat(columns);
return inst;
},
promise() {
if (!this.table) throw "no table specified";
// build SQL and parameter list
let sql = "SELECT *";
let params = [];
if (this.columns.length && this.columns != "*") {
sql = "SELECT ??";
params.push(this.columns);
}
sql += " FROM ??";
params.push(this.table);
if (this.conditions.length) {
sql += " WHERE " + this.conditions.map(cmp => `?? ${cmp} ?`).join(" AND ");
params.push(...this.params);
}
if (this.order.length) {
sql += " ORDER BY ??";
params.push(this.order);
}
return new Promise(resolve => {
connection.query(sql, params, function (error, results) {
if (error) throw error;
resolve(results);
});
});
}
};
// demo
sql.from("customer")
.select("id", "name")
.where("name", ">", "john")
.where("name", "<", "thomas")
.orderBy("name", "id")
.promise()
.then(console.log);
Note that in this implementation it does not matter in which order you chain the from, select, where and order method calls. You could even do the following if you wanted to:
sql .orderBy("name", "id")
.where("name", ">", "john")
.from("customer")
.where("name", "<", "thomas")
.select("id", "name")
.promise()
.then(console.log);

Nested Firebase Firestore forEach promise queries

I am using Firebase Cloud Firestore, however, I think this may be more of a JavaScript promise issue.
I have a collection called "students" which I am querying. For each found student I want to issue another query to find "parents" related by id.
For this, I have to nest a promise / foreach query and result inside another promise / foreach query and result.
Its currently executing the entire "students" promise/loop, returning from the function, then executing each of the "parents" promise/loops afterwards.
I want it to step through one found student, then execute all parents for that student, then go onto the next student, then return from the function.
getStudents(grade) {
let grade_part = this.getGrade(grade);
var dbRef = db.collection("students");
var dbQuery = dbRef.where('bb_current_grade', '==', grade_part);
var dbPromise = dbQuery.get();
var allStudents = [];
return dbPromise.then(function(querySnapshot) {
querySnapshot.forEach(doc => {
console.log("student");
var studentPlusParents = doc.data();
studentPlusParents.parents = [];
var dbRef = db.collection("parents");
var dbQuery = dbRef.where('student_id', '==', doc.data().id);
var dbPromise = dbQuery.get();
dbPromise.then(function(querySnapshot) {
querySnapshot.forEach(parentDoc => {
console.log("Add parent");
studentPlusParents.parents.push(parentDoc.data());
});
});
//console.log(studentPlusParents);
allStudents.push(studentPlusParents)
});
//console.log(allStudents);
return Promise.all(allStudents);
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});
}

Firebase value is undefined when it is not supposed to be

I am working on a firebase project. During testing the
return user.val().name;
will return an
undefined
value however the
console.log(user.val().name)
will return the actual string stored in the .name field. Why is that. Also even if assign the
user.val().name
to a variable, the variable remains undefined.Please help figure out why this happens. I am printing it to a csv.
Here is my code:
var database = firebase.database();
var ref2 = database.ref('information/');
var id;
var name;
ref2.on("value", function (one) {
one.forEach(function (two) {
if (typeof two.val().Id !== 'undefined') {
id = two.val().Id;
name = program(id); //name undefined
}
else {
id = "";
}
csv = name + "," + id +"\n";
});
download(csv);
});
};
function program (id) {
var database = firebase.database();
var ref = database.ref("users/" + id + "/");
ref.on('value',function(user){
if (typeof user.val().name === 'undefined') {
return null;
}
else {
console.log(user.val().name); //this doesnt show undefined
return user.val().name; //this shows undefined when appended to a html element
}
})
}
Note: In the firebase database, the name value is not null. It has a string added to it.
I second with Frank's reason on why your function program() doesn't work. Because ref.on('value'... makes an asynchronous call, program() does not wait for the completion of ref.on and exists with an undefined return value.
What you could instead do is use Promises. Wrap the statements inside your program() function within a Promise, and upon completion of the asynchronous call, resolve or reject based on the result it gives.
Here's your function with Promises:
function program(id) {
return new Promise(function (resolve, reject) {
try {
var database = firebase.database();
var ref = database.ref("users/" + id + "/");
ref.on('value', function (user) {
if (typeof user.val().name === 'undefined') {
resolve(null);
} else {
console.log(user.val().name);
resolve(user.val().name);
}
})
} catch (e) {
reject(e)
}
});
}
And then, here's how you can read the result:
program(id).then(function (result) {
console.log(result)
//Do what you want with the result here
}).catch(function (error) {
console.log(error)
})
Note: You're executing this block in a for-each statement. If you're using Promises, you'd also need to look into how to use Promises inside a loop. For reference, check Promise.all()
Most likely you are trying to use the returned name in the code that calls your program function. E.g.
var name = program("1234");
console.log(name); // this will print undefined
This will not work, since your program() is not actually returning name. Data is loaded from Firebase asynchronously. By the time program() exits, the data isn't loaded yet.
This is easiest to see by putting a few log statements into the code:
function program (id) {
var database = firebase.database();
var ref = database.ref("users/" + id + "/");
console.log("Before attaching listener");
ref.on('value',function(user){
console.log("Got value from database");
})
console.log("After attaching listener, exiting function");
}
This will print:
Before attaching listener
After attaching listener, exiting function
Got value from database
This is likely not the order that you expected, but it is working as designed. Instead of waiting for the data to be loaded (and making the browser/user wait), your code continues. Then when the data is loaded, your callback is invoked. But in your original code that means that your return statement is unable to return the name to the original caller.
This is precisely the reason why the Firebase Database (and most web APIs) use callbacks like the one you pass into on(). This callback is invoked when the data is available and is the only place where you can access the user data. So any code that requires the data you just loaded must be inside that callback, or be called from inside that callback. E.g.
function program (id) {
var database = firebase.database();
var ref = database.ref("users/" + id + "/");
ref.on('value',function(user){
if (typeof user.val().name === 'undefined') {
return null;
}
else {
console.log(user.val().name);
appendToHtmlElement(user.val().name);
}
})
}
Firebase> DataBase> Role
Have you changed the value of rules?
Comment Example!
That can get the value after moving to name.
var ref = database.ref ("users/" + id + "/name");
ref.on ('value', function (user) {
    if (user.val ()! = null) {
         console.log (user.val ())
     }
}
If not, let's worry about it.
You should receive the returned value.
var name = ref.on('value',function(user){
if (typeof user.val().name === 'undefined') {
return null;
}
else {
console.log(user.val().name); //this doesnt show undefined
return user.val().name; //this shows undefined when appended to a html element
}
})
then use return name to get the value. or simply return the return ref.on('value' ...

Categories