NodeJS MVC asynchronous request - node.js

I'm getting undefined data from controller to model request in NodeJS.
My Model:
const db = require('../util/database');
module.exports = class Property {
constructor(pID) {
}
getPropertyByID(pID) {
let property = {};
db.execute('SELECT * From Properties Where PropertyID = ' + pID)
.then(res => {
const data = res[0][0];
if(data){
//console.log(data); This shows DATA if I remove the comment
property = data;
}
return property;
})
.catch(err => {
console.log(err);
});
}
}
Then, in my controller I tried to use the Model method.
exports.sendDataID = (req, res, next) => {
const propID = req.params.propID;
if(propID) {
const property = new Property();
const data = property.getPropertyByID(propID);
console.log(data);
}
};
The console.log(data) is returning undefined. Why is it happening?
Thanks

You may use async and await function to return a promise result.
async getPropertyByID(pID) {
let property = {};
try{
var res = await db.execute('SELECT * From Properties Where PropertyID = ' + pID)
property = res[0][0]; //not sure what res variable is
return data
}catch(error){
return error
}
}
and for use this function you only need to do this:
const propID = req.params.propID;
if(propID) {
const property = new Property();
const data = await property.getPropertyByID(propID);
console.log(data);
}
I recomend you to read about promises functions: https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Objetos_globales/Promise

Related

Async function to scrape subreddits using Cheerio returns undefined

The script by itself works great (entering the url manually, writing a json file using the fs module, node script_name.js) but within a Express get request it returns undefined.
So I've built a simple frontend to let the user enter the subreddit name to be scraped.
And here's where the problem is:
Express controller
const run = require("../run");
requestPosts: async (req, res) => {
try {
const { subreddit } = req.body;
const response = await run(subreddit);
//console.log(response);
res.json(response);
} catch (error) {
console.error(error);
}
},
Cheerio functions
const axios = require("axios");
const { load } = require("cheerio");
let posts = [];
async function getImage(postLink) {
const { data } = await axios(postLink);
const $ = load(data);
return $("a.post-link").attr("href");
}
async function run(url) {
try {
console.log(url);
const { data } = await axios(url);
const $ = load(data);
$(".thing.linkflair.link").map(async (i, e) => {
const title = $(e)
.find(".entry.unvoted .top-matter .title .title")
.text();
const user = $(e)
.find(".entry.unvoted .top-matter .tagline .author")
.text();
const profileLink = `https://old.reddit.com/user/${user}`;
const postLink = `https://old.reddit.com/${$(e).find("a").attr("href")}`;
// const thumbail = $(e).find("a img").attr("src");
const image = await getImage(postLink);
posts.push({
id: i + 1,
title,
postLink,
image,
user: { user, profileLink },
});
});
const nextPage = $(".next-button a").attr("href");
if (nextPage) {
await run(nextPage);
} else {
return posts;
}
} catch (error) {
console.error(error);
}
}
module.exports = run;
I've tried working with Promise((resolve, reject) => {}).
I think it's returning undefined because maybe the code its not synchronized.
(idk if it makes sense, i've just started programming)
.map() is not promise-aware and does not wait for your promises to finish. So, $(".thing.linkflair.link").map() finishes long before any of the asynchronous functions inside its callback do. Thus you try to return posts BEFORE it has been populated.
Passing an async callback to .map() will return an array of promises. You can use Promise.all() on those promises to know when they are done and once you're doing that, you may as well just return each post object rather that using a higher level scoped/shared object, thus making the code more self contained.
I would suggest this code:
async function run(url) {
try {
console.log(url);
const { data } = await axios(url);
const $ = load(data);
const posts = await Promise.all($(".thing.linkflair.link").map(async (i, e) => {
const title = $(e)
.find(".entry.unvoted .top-matter .title .title")
.text();
const user = $(e)
.find(".entry.unvoted .top-matter .tagline .author")
.text();
const profileLink = `https://old.reddit.com/user/${user}`;
const postLink = `https://old.reddit.com/${$(e).find("a").attr("href")}`;
// const thumbail = $(e).find("a img").attr("src");
const image = await getImage(postLink);
// return a post object
return {
id: i + 1,
title,
postLink,
image,
user: { user, profileLink },
};
}));
const nextPage = $(".next-button a").attr("href");
if (nextPage) {
const newPosts = await run(nextPage);
// add these posts to the ones we already have
posts.push(...newPosts);
}
return posts;
} catch (error) {
console.error(error);
}
}

Problem to use a Map in Firebase Functions

I am trying to get the length of a Map and I keep getting "undefined". Could please someone tell me what am I doing wrong?
This is the part of the code that gives me problems.
const GYMdetail: { [key: string]: number} = {};
GYMdetail[`${doc.data().name} (${doc.data().personalID})`] = 650;
const subtotal = 650 * GYMdetail.size;
This is the complete function code
export const addGymMonthlyExpense =
functions.https.onRequest((request, response) => {
const query1 = admin.firestore().collection("users");
const query = query1.where("subscriptions.gym.active", "==", true);
query.get()
.then(async (allUsers) => {
allUsers.docs.forEach(async (doc) => {
if (doc != undefined) {
const houseForGym = doc.data().subscriptions.gym.house;
await admin.firestore()
.doc(`houses/${houseForGym}/expenses/2022-04`)
.get().then((snapshot) => {
if (snapshot.data() == undefined) {
console.log(`${houseForGym}-${doc.data().name}: CREAR!!`);
} else if (snapshot.data()!.issued == false) {
let detail: { [key: string]: any} = {};
const GYMdetail: { [key: string]: number} = {};
detail = snapshot.data()!.detail;
GYMdetail[
`${doc.data().name} (${doc.data().personalID})`
] = 650;
const subtotal = 650 * GYMdetail.size;
detail["GYM"] = {"total": subtotal, "detail": GYMdetail};
snapshot.ref.set({"detail": detail}, {merge: true});
}
return null;
})
.catch((error) => {
console.log(
`${houseForGym} - ${doc.data().name}: ${error}`);
response.status(500).send(error);
return null;
});
}
});
response.send("i");
})
.catch((error) => {
console.log(error);
response.status(500).send(error);
});
});
Since you are executing an asynchronous call to the database in your code, you need to return a promise from the top-level code; otherwise Cloud Functions may kill the container when the final } executes and by that time the database load won't be done yet.
So:
export const addGymMonthlyExpense =
functions.https.onRequest((request, response) => {
const query1 = admin.firestore().collection("users");
const query = query1.where("subscriptions.gym.active", "==", true);
return query.get()
...
Next you'll need to ensure that all the nested get() calls also get a chance to finish before the Functions container gets terminated. For that I recommend not using await for each nested get call, but a single Promise.all for all of them:
query.get()
.then(async (allUsers) => {
const promises = [];
allUsers.docs.forEach((doc) => {
const houseForGym = doc.data().subscriptions.gym.house;
promises.push(admin.firestore()
.doc(`houses/${houseForGym}/expenses/2022-04`)
.get().then((snapshot) => {
...
});
});
response.send("i");
return Promise.all(promises);
})
.catch((error) => {
console.log(error);
response.status(500).send(error);
});

How to return data in an async function in node.js? My code return [object Promise] but I want the response.length

I want the array length, but the function returns [object Promise]. I send a correct email and I just need know if it's already in collection.
const res = require('express/lib/response');
async function emailUnique(email){
var query = { email: email };
const response = await dbo.collection('users').find(query).toArray();
const tot = response.length;
return tot;
}
const emailValidate = function (email) {
if (email.indexOf('#') === -1 || email.indexOf('.') < 0) {
message = 'Invalid entries. Try again.';
}else{
let quantos = emailUnique(email);
message = quantos;
}
return message;
};
module.exports = emailValidate;
Thanks, Its a simplified answer:
emailValidate.mod.js
const res = require('express/lib/response');
function emailValidate(email) {
return new Promise((resolve) => { // return a promise
var query = { email: email };
dbo.collection('users').find(query).toArray((_err, result) => {
resolve(result.length); //the returned value
});
});
}
module.exports = emailValidate;
user.js
const mod_emailValidate = require('./emailValidate.mod');
exports.getUsers = (req, res) => {
const { name } = req.body;
const { email } = req.body;
const { password } = req.body;
async function run() { // make an async function
let data = await mod_emailValidate(email); //call the function with await
console.log(`Retornou: ${data}`);
}
run();
res.status(400).send({ message: 'Fired' });
};

Sequelize as a function in express JS always return isFulfilled and isRejected false

i have a helper like this
let connection = require('./connection');
const Sequelize = require('sequelize');
const sysUserModel = require("./models/sys_user");
const Sys_user = sysUserModel(connection, Sequelize);
let queryUtils = {
getOldPassw: function (SYSUSER_ID) {
return Sys_user.findOne(
{
attributes: ['SYSUSER_PASSW'],
where : {
SYSUSER_ID: SYSUSER_ID
}
}
).then(function (row) {
return row.dataValues.SYSUSER_PASSW;
});
}
};
exports.data = queryUtils;
And this is my controller
let queryUtils = require('../queryUtils');
let changePassword = function (req, res) {
let oldPass = queryUtils.data.getOldPassw('1010001');
return res.send(oldPass);
};
module.exports = {
changePassword
};
When i run my code, appears as below
errorRestClient
And this is my console
console
Would you like to help me to solve this case?
Thank u very much
You're returning a promise from your getOldPassw function, so you need to operate on that. Currently, you're sending the promise to the client and not the value that the promise resolves to.
let changePassword = function (req, res) {
queryUtils.data.getOldPassw('1010001')
.then(oldPass => res.send(oldPass))
.catch(err => {
// Always good to handle errors!
console.error(err);
res.sendStatus(500);
});
};

How to update document by ID without using model

I have Card model and I have an API where I'm looking for a document by ID.
app.post("/api/postcomment", async (req,res) => {
const data = req.body
const reqUrl = req.headers.referer
const re = new RegExp('([a-zA-Z0-9]*$)', 'i')
const fixedUrl = reqUrl.match(re)
try {
await Card.update({_id: fixedUrl}, {$push:{'comments': data}})
const card = await Card.findById(fixedUrl)
return res.json(card)
} catch (err) {
throw err
}
})
It works fine. But now I have few more models. All should work the same way to them. But how can I make this code reusable for every model?
Or maybe there is a way to pass a name of my model to API? and then use it like this:
app.post("/api/postcomment", async (req,res, modelName) => {
const data = req.body
const reqUrl = req.headers.referer
const re = new RegExp('([a-zA-Z0-9]*$)', 'i')
const fixedUrl = reqUrl.match(re)
try {
await modelName.update({_id: fixedUrl}, {$push:{'comments': data}})
const item = await modelName.findById(fixedUrl)
return res.json(item )
} catch (err) {
throw err
}
})
Solution1: You can create two helper functions and call the from the router. Both function accept the model object:
let updateDocument = (model, fixedUrl, data) => {
return model.update({ _id: fixedUrl }, { $push: { comments: data }})
}
let getDocument = (model, fixedUrl) => {
return model.findById(fixedUrl)
}
app.post("/api/postcomment", async (req, res, modelName) => {
const data = req.body
const reqUrl = req.headers.referer
const re = new RegExp('([a-zA-Z0-9]*$)', 'i')
const fixedUrl = reqUrl.match(re)
try {
await updateDocument(Card, fixedUrl, data)
const item = await getDocument(Card, fixedUrl)
return res.json(item )
} catch (err) {
throw err
}
})
Solution2: The much better solution is to create a base class (service), with the common functionality. And extend it for each model:
class BaseService {
constructor(model) {
this.model = model;
}
getDocument(data) {
return this.model.findOne(...);
}
updateDocument(data) {
return this.model.update(...);
}
}
class CardService extends BaseService {
constuctor() {
super(Card);
}
}

Resources