I am working on a node.js which has multiple steps with Mongo:
Get a document
Push a subdocument into the subarray and save
Do an aggregation framework query to get the inserted subdocument
Return it to the calling function
My function:
addItem(itemId, data1, data2) {
return new Promise(function(resolve, reject) {
let item = Model.findOne({'_id': new ObjectID(itemId)});
resolve(item);
}).then(function(item) {
// STEP 2
const id = new ObjectID();
item.stuff.push({data1: .... })
item.save();
return { item: item, id: id }
}).then(function(res) {
return Model.aggregate([ .... ])
}).then(function(res) {
return res;
})
}
The problem is, I call my function addItem,
addItem(itemId, data1, data2).then(res => { PROBLEM })
where PROBLEM returns the call definition to my aggregation framework.
Example:
console.log(PROBLEM);
Aggregate {
_pipeline: [
.. items
],
_model: Model {},
options: {}
}
How do I fix it so my aggregation framework returns data? I think it has something to do w/ STEP 2, especially considering .save() returns a Promise
Ok, so it looks like I did the Promise and async / await backwards.
The solution:
In Express, my endpoint:
app.post('/endpoint', (req, res) => {
new Promise(resolve => {
addMessage(currentUserId, chatId, msg).then(res => {
console.log(res);
res.sendStatus(codes.CREATED).send(res);
});
});
});
and my addMessage function:
addMessage = async function (userId, chatId, message) {
let item = await Model.findOne({ .... });
let id = new ObjectID();
item.stuff.push({data1: .... });
await item.save();
let retval = await Model.aggregate([ .... ]);
return retval;
}
Related
I`m creating API on my express.js server, so when it takes "get" request, some function placed in module file asking data from graph db:
module.js file:
function getGraphData() {
const cityName = 'Milan';
const readQuery = `MATCH (City {name: $cityName})-[:LINKED*1..3]-(city:City) RETURN city`;
const cities = [];
session
.run(readQuery, { cityName })
.then(function (result) {
result.records.forEach(function (record) {
cities.push({
title: record._fields[0].properties.name,
});
});
return cities;
})
.catch((err) => console.log(err));
}
module.exports.getGraphData = getGraphData;
After receiving data it stores in array named cities and looks like this:
cities: [ { title: 'City1' }, { title: 'City2' }, { title: 'City3' } ]
So, function is returning this array, and then I import function from module and use it in my router file:
const { getGraphData } = require('./../db/neo4j');
router.get('/', async (req, res) => {
try {
const p = await getGraphData();
console.log('p:', p); //p is undefined
//return res.status(200).send(p);
return res.status(206).json(p); // empty response, not any data only status code
} catch (e) {
console.log(e);
});
So, what I'm doing wrong? Why does api response is empty?
Im use debugger. Data realy comes to function in module, but doesn`t passing to api response to "p" variable.
Your getGraphData function is using .then . When it executes it makes the session call and returns immediately that is why it returns empty.
Although, you are doing await on getGraphData, your getGraphData function needs to be defined as async and use await with session for it to work.
async function getGraphData() {
const cityName = 'Milan';
const readQuery = `MATCH (City {name: $cityName})-[:LINKED*1..3]-(city:City) RETURN city`;
const cities = [];
try{
const result = await session.run(readQuery, { cityName });
result.records.forEach(function (record) {
cities.push({
title: record._fields[0].properties.name,
});
});
return cities;
}
catch(err){
console.log(err);
return err;
}
}
I've been making dynamic dropdown box that each option has the table's name of BigQuery and I want to use return value (list) that is made inside .then method in function listTables(). However it seems not to work well . I'm new to Js so could you give any tips ?? Thank you so much.
function listTables() {
const {BigQuery} = require('#google-cloud/bigquery');
const bigquery = new BigQuery({
projectId: 'test_project',
});
const list = [];
bigquery
.dataset("test_table")
.getTables()
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
return console.log(list);←I want to use this list outside a function
})
.catch(err => {
console.error('ERROR:', err);
});
}
listTables();
// select tag
let slt = document.getElementById("slt");
addTables(slt);
// return data
function getList() {
return new Promise(function (onFulliflled, onRejected) {
onFulliflled(list);
});
}
function addTables(slt) {
getList()
.then((list) => {
for (item of list) {
// optionを作成
let option = document.createElement("option");
option.text = item;
option.value = item;
// optionの追加
slt.appendChild(option);
}
})
.catch((err) => {
console.error("error", err);
});
}
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
return console.log(list);
})
RESULT
[ 'test', 'test1', 'test2', 'test3' ]
You can do this in multiple ways.
Using calllback
function listTables(callback) {
//...
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
callback(list);
})
//...
}
listTables(function(list){
});
Using promise or async/await
function listTables() {
return new Promise(function(resolve, reject){
//...
.then(results => {
const tables = results[0];
tables.forEach(table => list.push(table.id));
resolve(list);
})
//...
});
}
// Promise
listTables().then(function(list){
});
//Async/await
var list = await listTables();
For the await to work you also need to run in within an async function. For example wrap it in async iife.
(async function(){
var list = await listTables();
})();
I don't use await myself so this is just from top of my head and might need some changes.
I am implementing a Cloud Function where I am executing several queries.
let rating_subcollection = await admin.firestore().collection("restaurants_collection").doc(vendor_id).collection('ratings').where('uID', '==', userId)
.get()
.then(async function (data) {
if (data.empty) {
let restaurants_collection = admin.firestore().collection("restaurants_collection").doc(vendor_id);
await admin.firestore().runTransaction(async (transaction) => {
const restDoc = await transaction.get(restaurants_collection);
// Compute new number of ratings
const newNumRatings = restDoc.data().noRat + 1;
// Compute new average rating
const oldRatingTotal = restDoc.data().rat * restDoc.data().noRat;
const newAvgRating = (oldRatingTotal + ratingVal) / newNumRatings;
// Update restaurant info
transaction.update(restaurants_collection, {
rat: newAvgRating,
noRat: newNumRatings
});
})
}
}).catch(error => {
return "Couldnt update the rating: " + error;
})
So, as you can see I am only executing the transaction IF data is empty and I have set async in the then() callback. Is this the right way to do?!
I found this example, where is explained on detail how get a collection using an async function, for example if you want to get a collection:
function getValues(collectionName, docName) {
return db.collection(collectionName).doc(docName).get().then(function (doc) {
if (doc.exists) return doc.data().text;
return Promise.reject("No such document");
}};
}
Or using await it inside an async function in a try/catch block, if you like that better:
async function doSomething() {
try {
let text = await getValues('configuration','helpMessage');
console.log(text);
} catch {
console.log("ERROR:" err);
}
}
I'm using Nodejs with MongoDB(mongoose along with express).
Since I don't trust the user data, I need to verify it from the database.
input data:
{
"id": "someid",
"nottrusteddata": [ {"id": "1"}, {"id" :"2"}]
}
In my function, I'm verifying the data:
router.post("/validate", (req, res,next) =>{
let validated_data = validate_data(req);
console.log(JSON.stringify(validated_data));
const mydata = new Mydata({
id: req.body.id,
lst : validated_data
});
console.log("mydata: " + JSON.stringify(mydata));
/* Some Usefull stuff is here */
res.status(200).json();
}
function validate_data(req){
let validated_data = []
for(let i = 0; i < req.body.nottrusteddata.length; i++)
{
Databaseobject.findOne({'id': req.body.nottrusteddata[i].id})
.exec()
.then(dbobject =>{
if(dbobject) // not undefined, it exists in the database
{
// Some logic with the object returned from the database
let tmp_object = {};
tmpobject.id = dbobject.id;
// Append it to the list, so that the upper function can use it
validated_data.push(tmp_object);
}
})
}
return validated_data;
}
The desired output should contain the correct information coming from the database, however, due to the async nature of the nodejs, validated_data returns null.
I have also tried using Promise. I couldn't succeed it.
const validate_data = function(req){
return new Promise(function(resolve,reject){
let validated_data = []
for(let i = 0; i < req.body.nottrusteddata.length; i++)
{
Databaseobject.findOne({'id': req.body.nottrusteddata[i].id})
.exec()
.then(dbobject =>{
if(dbobject) // not undefined, it exists in the database
{
let tmp_object = {};
tmpobject.id = dbobject.id;
validated_data.push(tmp_object);
}
})
}
resolve(validated_data);
}
}
What am I doing wrong? How can I wait for the database query to finish, then execute the main part? If there is only one validation, I could've used .then(). However, the list might have contained many elements and I need to wait for all of them to be verified.
Your Databaseobject.findOne() calls are asynchronous so your promise will resolve before any of them complete.
You can make use of Promise.all to wait until all of your promises resolve.
Hopefully, this will work for you:
router.post("/validate", (req, res) => {
validate_data(req.body.nottrusteddata)
.then(validated_data => {
const mydata = new Mydata({
id: req.body.id,
lst: validated_data
})
// Some useful stuff is here
res.status(200).json()
})
.catch(err => {
// Handle error
})
}
function validate_data(nottrusteddata) {
// Create array of pending promises
const promises = nottrusteddata
.map(item => {
return Databaseobject
.findOne({ 'id': item.id })
.exec()
})
// Wait for all promises to resolve
return Promise.all(promises)
.then(docs => {
return docs
.filter(dbobject => dbobject) // Filter out undefined
.map(dbobject => {
return { id: dbobject.id }
})
})
}
If you want, you could also use async-await here:
router.post("/validate", async (req, res) => {
try {
const validated_data = await validate_data(req.body.nottrusteddata)
const mydata = new Mydata({
id: req.body.id,
lst: validated_data
})
// Some useful stuff is here
res.status(200).json()
}
catch(err) {
// Handle error
}
})
i am updating 2 documents using mongoose.save(), but i think the way I am doing is is not safe, as far as i know i need to use async to make sure all documents are being executed
// array containing the 2 documents from db
let schedules
let newItem = {
isActive: room.isActive,
name: room.roomname
};
// adding new items to nested array
schedules[0].rooms.push(newItem);
schedules[1].rooms.push(newItem);
// saving / updating documents
var total = schedules.length,
result = [];
function saveAll() {
var doc = schedules.pop();
doc.save(function(err, saved) {
if (err) throw err; //handle error
result.push(saved);
if (--total) saveAll();
else {
// all saved here
res.json(result);
}
});
}
saveAll();
any explanation how to do it correctly
We can use promise.all for this but we need to change your save function to promise based function
...
var total = schedules.length,
result = [];
function saveAll() {
const promises = schedules.map(schedule => save(schedule));
return Promise.all(promises)
.then(responses => {
// all saved processes are finished
res.json(responses);
})
}
// convert callback `save` function to promise based
function save(doc) {
return new Promise((resolve, reject) => {
doc.save((err, saved) => {
if (err) {
reject(err);
}
resolve(saved);
});
});
}
If you can use async/await we can make saveAll function cleaner
async function saveAll() {
const promises = schedules.map(schedule => save(schedule));
const responses = await Promise.all(promises);
res.json(responses);
}
Hope it helps
Use Promises :
doc1.save().exec().then(
data => {
doc1.save().exec().then(
data2 => console.log(data2)
).catch(err2 => console.log(err2))
}
).catch(err1 => console.log(err1))