Firebase firestore & Express - build json getting nested documents - javascript

So far I am building an app using express and firebase cloud functions. I am not able to create a nested json according to this db:
Here is the code:
exports.tstMenu = (req, res) => {
let shop = db.collection('shops').doc(req.params.name).collection('menus');
shop.get()
.then((data) => {
let menu = [];
data.forEach((doc) => {
let categories = getCategories(doc.id, shop);
menu.push({
menuID: doc.id,
name: doc.data().name,
position: doc.data().position,
categories: categories,
});
console.log(menu);
});
return res.json(menu);
})
.catch((err) => {
console.error(err);
return res.status(500).json({ error: err.message});
});
}
function getCategories(id, db){
let shop = db.doc(id).collection('categories');
return shop.get()
.then((data) => {
let categs = [];
data.forEach((doc) => {
var menuElements = [];//getMenuElement(doc.id, shop);
categs.push({
catID: doc.id,
name: doc.data().name,
position: doc.data().position,
menuElements: menuElements,
});
});
return categs;
});
}
and the result of tstMenu is:
while the log is showing this:
Can anyone explain me how to fix it? I am quite sure that promises are not received when tstMenu reaches return res.json(menu);

Your problem lies within this line :
let categories = getCategories(doc.id, shop);
getCategories is an async method. It returns a promise so you can't use it directly as you do.
You either should do your assignment in a then callback or you should use async await.
exports.tstMenu = (req, res) => {
let shop = db.collection('shops').doc(req.params.name).collection('menus');
shop.get()
.then((data) => {
let menu = [];
const promises = data.docs.map((doc) => // change this from forEach to map
getCategories(doc.id, shop).then(categories =>{
menu.push({
menuID: doc.id,
name: doc.data().name,
position: doc.data().position,
categories: categories,
});
);
return Promise.all(promises).then(()=> res.json(menu)); // return res after all promises completed
})
.catch((err) => {
console.error(err);
return res.status(500).json({ error: err.message});
});
}
Or
exports.tstMenu = async (req, res) => {
try {
let shop = db.collection('shops').doc(req.params.name).collection('menus');
const data = await shop.get()
let menu = [];
const promises = data.docs.map((doc) => // change this from forEach to map
getCategories(doc.id, shop).then(categories =>{
menu.push({
menuID: doc.id,
name: doc.data().name,
position: doc.data().position,
categories: categories,
});
);
await Promise.all(promises);
return res.json(menu)
} catch(err) {
console.error(err);
return res.status(500).json({ error: err.message});
}
}

Related

Return promise in node.js

i try to return data in node.js from a APIs , but I'm having problems, because I need an asynchronous function, I couldn't understand for sure the correct use of the promise, I've tried everything and I couldn't put the result in the return, only in the console.log, somebody help me?
const express = require('express')
const MFA = require('mangadex-full-api')
module.exports = {
async indexManga(req, res) {
const mangalist = MFA.login('DarksGol', 'R#ul1605', './md_cache/').then(async () => {
manga = []
await MFA.Manga.search('Kiss').then(results => {
results.forEach((elem, i) => {
let obj = {}
obj.status = elem.status
obj.title = elem.title
manga.push(obj)
})
}).catch(console.error)
return manga
}).catch(console.error)
console.log(await mangalist)
return mangalist
}
}
no error occurred, only infinite loading on request
const express = require('express')
const routes = express.Router()
const searchManga = require('../src/controllers/searchManga')
routes.get('/searchManga', searchManga.indexManga)
module.exports = routes
Looks like indexManga is an endpoint. Each endpoint function must end the request-response cycle by sending a response ( res.send(), res.json(), res.end(), etc). If indexManga s an endpoint, the solution would be:
...
//return mangalist
res.send(mangalist)
or
//return mangalist
res.json({ status: "success", message: "logged in successfully" })
If it is a meddleware:
async indexManga(req, res, next) {
...
//return mangalist
return next()
}
EDIT: you are using async/await with .then() improperly in some places.
Try this way:
module.exports = {
indexManga(req, res) {
MFA.login('DarksGol', 'R#ul1605', './md_cache/').then(() => {
manga = []
MFA.Manga.search('Kiss').then(results => {
results.forEach((elem, i) => {
let obj = {}
obj.status = elem.status
obj.title = elem.title
manga.push(obj)
})
res.json({ status: "success", data: manga })
}).catch((err) => {
res.json({ status: "fail", error: err })
})
}).catch((err) => {
res.json({ status: "fail", error: err })
})
}
}
I don't see why this would cause "infinite loading on request", but you could greatly simplify the code to just
const express = require('express');
const MFA = require('mangadex-full-api');
module.exports = {
async indexManga(req, res) {
await MFA.login('DarksGol', 'R#ul1605', './md_cache/')
const mangaList = [];
const results = await MFA.Manga.search('Kiss');
results.forEach(elem => {
mangaList.push({
status: elem.status,
title: elem.title,
});
});
return mangalist;
},
};

How can i use promise that coming from another page in node.js

I have 2 pages in my controller folder like this:
General.js
const Person = require('../models/person');
let personInfo = new Promise((success,reject)=>{
Person.find({ Group: 'pre'})
.then((par) => {
if (par.length > 0) {
success(par);
} else {
reject("Error");
}
})
.catch((err) => { console.log(err); });
});
module.exports.personInfo = personInfo;
Account.js
exports.Ac = (req, res, next) => {
let person = new require('./general');
person.personInfo
.then((par) => {
return res.render('/myPage/Account', {
title: 'Account',
group: par
});
})
.catch((err) => { console.log(err); });
}
Problem is , this promise working when server beginning but only once after its not working , par value always being same. If i change datas on my database , datas not changing on my web page.
anti-pattern
This is the explicit promise construction anti-pattern -
let personInfo = new Promise((success,reject)=>{
Person.find({ Group: 'pre'})
.then((par) => {
if (par.length > 0) {
success(par);
} else {
reject("Error");
}
})
.catch((err) => { console.log(err); });
});
You can replace it with -
let personInfo = Person.find({ Group: 'pre'})
.then((par) => {
if (par.length > 0) {
return par
} else {
throw Error("Error: empty par");
}
})
.catch(console.error) // <- don't catch here
And you should probably leave the .catch off and expect the caller to handle error handling. The .catch handler below would never trigger if the error is catch'd before
exports.Ac = (req, res, next) => {
let person = new require('./general');
person.personInfo
.then((par) => {
res.render('/myPage/Account', { // no "return" needed
title: 'Account',
group: par
});
})
.catch((err) => { console.log(err); }); // <- keep catch here
}
The reason it is only happening once, is because Promises can only be resolved or rejected once. You'll have to replace personInfo with a function -
const Person = require('../models/person');
const fetchPersonInfo = () =>
Person.find({ Group: 'pre'})
.then((par) => {
if (par.length > 0) {
return par
} else {
throw Error("Error: empty par");
}
})
});
module.exports.fetchPersonInfo = fetchPersonInfo;
async await
You might also want to consider reading up on async/await as they make your life a lot better
const Person = require('../models/person');
async function fetchPersonInfo () { // <- async
const par = await Person.find({ Group: 'pre'}) // <- await
if (par.length > 0)
return par;
else
throw Error("Error: empty par");
});
module.exports.fetchPersonInfo = fetchPersonInfo;
const { fetchPersonInfo } = new require('./general'); // <- top-level import
exports.Ac = async (req, res, next) => { // <- async
try {
const par = await fetchPersonInfo() // <- await
res.render('/myPage/Account', {
title: 'Account',
group: par
});
} catch (err) {
console.log(err)
}
}

Async await on Nodejs API call

I'm trying to execute a function after another function (API call) has returned its result. The problem is, the program always ends up executing the second one before the first one has given the result.
The thing is, I need to place a contact email on a Mailing List using Mailjet, but first I have to create that contact. So, the contact creation works, but not the placement on the list, as this function is executed before the contact creation finishes.
I tried multiple things for some days, mostly using async/await, but I still don't get my head around it.
Here's my code:
routes/index.js
router.post('/', async (req, res, next) => {
const { email, name } = req.body;
const mktListId = process.env.MAILJET_ID_MARKETING;
try {
const contactCreated = await createContact(email, name);
addEmailToList(email, mktListId);
res.status(201).send({ message: 'Email Successfully subscribed to Marketing List' });
} catch (err) {
res.status(400).json({
status: 'fail',
message: err,
});
}
});
function createContact(email, name) {
const mailjet = require('node-mailjet').connect(
process.env.MAILJET_MASTER_APIPUBLIC,
process.env.MAILJET_MASTER_APISECRET
);
const request = mailjet.post('contact', { version: 'v3' }).request({
IsExcludedFromCampaigns: 'true',
Name: `${name}`,
Email: `${email}`,
});
request
.then(result => {
console.log('result mailjet create contact', result.body);
})
.catch(err => {
console.log('error mailjet create contact', err.statusCode, err.ErrorMessage);
});
}
function addEmailToList(email, listId) {
const mailjet = require('node-mailjet').connect(
process.env.MAILJET_MASTER_APIPUBLIC,
process.env.MAILJET_MASTER_APISECRET
);
const request = mailjet.post('listrecipient', { version: 'v3' }).request({
IsUnsubscribed: 'true',
ContactAlt: `${email}`,
ListID: `${listId}`,
});
request
.then(result => {
console.log('result mailjet add to list', result.body);
})
.catch(err => {
console.log('error mailjet add to list', err.statusCode, err.ErrorMessage);
});
}
Any help with be much appreciated. Thank you!
Without a promise, await doesn't really do anything.
await new Promise (resolve => {
console.log ("A");
resolve ();
});
await new Promise (resolve => {
console.log ("B");
resolve ();
});
await new Promise (resolve => {
console.log ("C");
resolve ();
});
The create function needs to look more like:
function createContact(email, name) {
return new Promise ((resolve, reject) => {
const mailjet = require('node-mailjet').connect(
process.env.MAILJET_MASTER_APIPUBLIC,
process.env.MAILJET_MASTER_APISECRET
);
const request = mailjet.post('contact', { version: 'v3' }).request({
IsExcludedFromCampaigns: 'true',
Name: `${name}`,
Email: `${email}`,
});
request
.then(result => {
console.log('result mailjet create contact', result.body);
resolve ();
})
.catch(err => {
console.log('error mailjet create contact', err.statusCode, err.ErrorMessage);
reject ();
});
});
}
For future reference, this is the clean finished and refactored code:
routes/index.js
const express = require('express');
const router = express.Router();
const emailController = require('../controllers/emailController');
router.post('/', subscriberValidationRules(), validate, emailController.create);
Then we put the router logic on:
controllers/emailController.js
const { createContact, addEmailToList } = require('../helpers/addEmailToList');
const create = async (req, res, next) => {
const { email, name } = req.body;
const mktListId = process.env.MAILJET_ID_MARKETING;
try {
await createContact(email, name);
await addEmailToList(email, mktListId);
return res.status(201).send({ message: 'Email Successfully subscribed to Marketing List' });
} catch (err) {
res.status(400).json({
status: 'fail',
message: err,
});
}
}
module.exports = {
create,
};
And then our functions on:
helpers/addEmailToList.js
const createContact = async (email, name) => {
try {
const mailjet = require('node-mailjet').connect(
process.env.MAILJET_MASTER_APIPUBLIC,
process.env.MAILJET_MASTER_APISECRET,
)
const { body } = await mailjet.post('contact', { version: 'v3' }).request({
IsExcludedFromCampaigns: 'true',
Name: `${name}`,
Email: `${email}`,
})
console.info('result mailjet create contact', body)
return body
} catch (err) {
console.info('error mailjet create contact', err.statusCode, err.ErrorMessage)
}
}
const addEmailToList = async (email, listId) => {
const mailjet = require('node-mailjet').connect(
process.env.MAILJET_MASTER_APIPUBLIC,
process.env.MAILJET_MASTER_APISECRET
);
try {
const { body } = await mailjet.post('listrecipient', { version: 'v3' }).request({
IsUnsubscribed: 'true',
ContactAlt: `${email}`,
ListID: `${listId}`,
});
console.info('result mailjet add to list', body);
return body;
} catch (err) {
console.info('error mailjet add to list', err.statusCode, err.ErrorMessage);
}
};
module.exports = {
createContact,
addEmailToList,
};

Query return as undefined using knex

I need to register a new user, when receiving the parameters make a query using the city name to get the state and city id (both are foreign keys). I implemented a function to find the ids. Inside the function using data.id the id is returned correctly. But at the time of insert in database is being inserted "undefined".
Apparently the save operation is being executed before the findCity and findState functions return the value.
execution flow
cidade = city, estado = city
module.exports = app => {
const obterHash = (senha, callback) => {
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(senha, salt, null, (err, hash) => callback(hash))
})
}
var idCidade;
var idEstado
function findCidade(cidade, ) {
app.db('cidades')
.where({ nome: cidade })
.first()
.then(data => {
idCidade = data.id
console.log('inside findCity. data.id: '+data.id)
}).catch((err) => console.log("erro cidade", err));
return
}
function findEstado(uf) {
app.db('estados')
.where({ uf: uf })
.first()
.then(data => {
idEstado = data.id
console.log('inside findState. data.id: '+data.id)
}).catch((err) => console.log("erro estado", err));
}
const save = (req, res) => {
console.log("\n")
findCidade(req.body.cidade)
findEstado(req.body.uf)
obterHash(req.body.senha, hash => {
const senha = hash
console.log("Will be inserted. idCity: "+idCidade+" idState: "+idEstado)
app.db('salao')
.insert({ idcidade: idCidade,
idestado: idEstado,
senha})
.then(_ => res.status(200).send())
.catch(err =>{res.status(400).json(err)})
})
}
return { save }
}
I'm from Brazil and I'm using a translator, sorry for the spelling mistakes.
You are welcome to the asynchronous world!
General explanation: You are going to use results of a database querying before it will happen. Your program have to wait the results (idCidade, idEstado) before you can use it. Because of it you can find the record Will be inserted... first in your logs.
For the explanation I'm going to use Minimal Reproducible Example.
function findCidade(cidade) {
return Promise.resolve(1);
}
function findEstado(uf) {
return Promise.resolve(1);
}
Promise.all([findCidade(), findEstado()])
.then((data) => console.log(data));
The output is:
[ 1, 1 ]
To solve the issue you have to:
Return the promise explicitly with return statement.
Await the results by async/await or Promise interface methods. Or use callbacks if it is more suitable to you.
module.exports = app => {
const obterHash = (senha, callback) => {
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(senha, salt, null, (err, hash) => callback(hash))
})
};
function findCidade(cidade, ) {
return app.db('cidades')
.where({ nome: cidade })
.first()
.then(data => {
idCidade = data.id
console.log('inside findCity. data.id: '+data.id)
}).catch((err) => console.log("erro cidade", err));
}
function findEstado(uf) {
return app.db('estados')
.where({ uf: uf })
.first()
.then(data => {
idEstado = data.id
console.log('inside findState. data.id: '+data.id)
}).catch((err) => console.log("erro estado", err));
}
const save = (req, res) => {
console.log("\n");
Promise.all([findCidade(req.body.cidade), findEstado(req.body.uf)])
.then((data) => {
const [idCidade, idEstado] = data;
obterHash(req.body.senha, hash => {
const senha = hash;
console.log("Will be inserted. idCity: "+idCidade+" idState: "+idEstado);
app.db('salao')
.insert({ idcidade: idCidade,
idestado: idEstado,
senha})
.then(_ => res.status(200).send())
.catch(err =>{res.status(400).json(err)})
})
})
.catch((err) => console.log("general error", err));
};
return { save }
}

Mongoose insertMany does not work for large array

I have been trying to insert large data about(400-1000) json object array to mongodb using mongoose + expressjs When i changed data about(50) items insertMany works great without problem. But if data is more than 100 it giving me an error.
Departed.insertMany(results)
.then(dep => {
console.log(dep)
res.sendStatus(201)
})
.catch(err => {
console.log(err)
})
in morgan console i got following:
creation { active: true,
_id: 5b73e8af19722d1689d863b0,
name: 'TEST DATA 241',
map: '',
created_at: 2018-08-15T08:47:43.196Z,
updated_at: 2018-08-15T08:47:43.196Z,
__v: 0 }
insert read 453
(node:5769) [DEP0079] DeprecationWarning: Custom inspection function on Objects via .inspect() is deprecated
also on client side(chrome, dev tools network tab) status got
(failed)
net::ERR_EMPTY_RESPONSE
I have read mongo's insertMany() has limit about 1000 and i am using mongo 4.0 version. Even i chunked large json into several arrays and tried to insert it but still got same results. Actual snippets are
router.post('/xls', upload.single('file'), async (req, res, next) => {
try {
if (req.body && req.file) {
console.log('req', req.file)
const segments = req.file.originalname.split('.')
let exceltojson = segments[segments.length - 1] === 'xlsx' ? xlsx : xls
exceltojson(
{
input: req.file.path,
output: 'output.json'
},
async (err, result) => {
if (err) console.log(err)
const section = await Section.create({
name: req.body.section,
map: req.body.map
})
const results = await result.map(item => {
return {
branch: req.body.branch,
section: String(section._id),
...item
}
})
await console.log('creation', section)
console.log('insert read', results.length)
if (results.length >= 100) {
console.log('more than 100')
const data = _.chunk(results, 100)
data.forEach(async chunk => {
console.log('foreach')
Departed.insertMany(chunk)
.then(dep => {
console.log(dep)
res.sendStatus(201)
})
.catch(err => {
console.log(err)
})
})
}
}
)
}
} catch (error) {
next(error)
}
})
Your problem is not related to any insertMany limit. You have a race condition in your code where you don't wait for all chunks to be inserted, before sending the status back:
data.forEach(async chunk => {
console.log('foreach')
Departed.insertMany(chunk)
.then(dep => { // this will be called as soon as one of the inserts finish
console.log(dep)
res.sendStatus(201)
})
.catch(err => {
console.log(err)
})
})
Change this in something like (untested):
Promise.all(data.map(chunk => Departed.insertMany(chunk)))
.then(dep => { // this will be called when all inserts finish
console.log(dep)
res.sendStatus(201)
})
.catch(err => {
console.log(err)
})
})
Another alternative is to use the bulkWrite API which is is faster than sending multiple independent operations because with bulkWrite() there is only one round trip to MongoDB:
router.post('/xls', upload.single('file'), async (req, res, next) => {
try {
if (req.body && req.file) {
console.log('req', req.file)
const segments = req.file.originalname.split('.')
let exceltojson = segments[segments.length - 1] === 'xlsx' ? xlsx : xls
exceltojson(
{
input: req.file.path,
output: 'output.json'
},
async (err, result) => {
if (err) console.log(err)
const section = await Section.create({
name: req.body.section,
map: req.body.map
})
let chunk = [];
result.forEach(item => {
chunk.push({
insertOne: {
document: {
branch: req.body.branch,
section: String(section._id),
...item
}
}
});
if (chunk.length === 500) {
const blkResult = await Departed.bulkWrite(chunk);
console.log(blkResult)
res.sendStatus(201)
}
});
if (chunk.length > 0) {
const dep = await Departed.bulkWrite(chunk);
console.log(dep)
res.sendStatus(201)
}
}
)
}
} catch (error) {
next(error)
}
})

Categories