How can I execute this transaction? My problem is the correct use of for instruction.
app.db.transaction(function (trx) {
app.db('sales_record_products').transacting(trx).insert(products)
.then(_ => {
for (let i = 0; i < stockProducts.ids.length; i++) {
app.db('products').transacting(trx).where('id',stockProducts.ids[i]).update({quantity: stockProducts.results[i]})
.then(_ => {
})
}
})
.then(trx.commit)
.catch(trx.rollback)
})
.then(_ => res.status(200).json({success: true}))
.catch(err => res.status(500).send(err))
Why not using something like
// Start a database transaction
return app.db.transaction(function (trx) {
// Insert products
return trx.insert(products)
.then(function (newIds) {
// Product inserted, you have the new database id in newIds
// Update all product individually but sequentially
return Promise.mapSeries(stockProducts, function (sp) {
// Update a single product
return trx
.table('products')
.where('id', stockProducts.ids[i])
.update({ quantity: stockProducts.results[i] });
});
});
});
Related
I'm new to javascript and i'm having a hard time making my response return to wait for my mongodb query finish running inside a forEach loop.
My code is currrently:
exports.applyThesaurus = (req, res, next) => {
let numModified = 0;
var prom = new Promise((resolve,reject) => {
req.file.forEach((obj,idx) => {
wos.updateMany(
{ creator: req.userData.userId},
{ $set: { [req.body.field+".$[element]"] : obj.replaceWith } },
{ arrayFilters: [ { "element": { $in: obj.replaced } } ] }
).then((result) => {
console.log(result.nModified)
numModified += result.nModified
})
.catch((err) => {
res.status(500).json('There was an error while applying the thesaurus.');
})
if( idx === req.file.length -1) {
resolve()
}
})
})
prom.then(() => {
console.log('im returning');
res.status(200).json({message: numModified + ' itens replaced successfully'});
})
}
What happens is that the "i'm returning" console log triggers before the one logging result.nModified
I need to be able to run all the updateMany queries and then respond with the number of updated itens.
Any tips? Thank you very much!
your code is trying to return resolve before your updateMany executes.
if (idx === req.file.length - 1) {
resolve() // this resolve get's executed befour updateMany get executed
}
this might give you a better understanding of the callbacks and why it is happening. as said if you want to resolve the promise after your updateMany finishes execution you need to update your code as below:
exports.applyThesaurus = (req, res, next) => {
let numModified = 0;
var prom = new Promise((resolve, reject) => {
let updateManyPromisesArray = []
req.file.forEach((obj, idx) => {
updateManyPromisesArray.push(wos.updateMany({
creator: req.userData.userId
}, {
$set: {
[req.body.field + ".$[element]"]: obj.replaceWith
}
}, {
arrayFilters: [{
"element": {
$in: obj.replaced
}
}]
}))
Promise.all(updateManyPromisesArray)
.then((result) => {
if (idx === req.file.length - 1) {
resolve()
}
})
.catch((err) => {
res.status(500).json('There was an error while applying the thesaurus.');
})
})
})
prom.then(() => {
console.log('im returning');
res.status(200).json({
message: numModified + ' itens replaced successfully'
});
})
}
Also, you should start using async and await for avoiding these kinds of callback hells situations.
I am trying to make a db call insides a loop and want to add that data into one object and then send this object to user, but I am getting only a empty object at the user end.
I already checked this one asynchronous response into a loop in javascript NodeJS
router.get('/collection', (req, res) => {
const Data = {}
Section.find({})
.then(sections => {
sections.map(section => {
let id = section._id;
Product.find({section: id})
.then(products => {
// console.log(products)
Data[section.title] = {
title: section.title,
routeName: section.title,
id,
items: products
}
console.log(Data)
})
.catch(err => {
return res.status(500).json(err)
})
})
return res.json(data)
})
.catch(err => {
return res.status(500).json(err)
})
})
I want the output to be like :-
{
food : {
items: [...]
},
vegetable: {
items: [...]
}
}
food and vegetable are keys which will be obtained from a Database Call and items in each keys are returned from a seperate call to database.
return res.json(data) is executed before any of the mapped Product-promises are resolved (also there's a typo since you're returning data instead of Data). One way to do this is to map the find-promises and to use Promise.all on the mapped array. Something like:
router.get('/collection', (req, res) => {
const Data = {}
Section.find({})
.then(sections => {
const sectionProductPromises = sections.map(section => {
let id = section._id;
return Product.find({
section: id
})
.then(products => {
Data[section.title] = {
title: section.title,
routeName: section.title,
id,
items: products
}
});
});
return Promise.all(sectionProductPromises);
})
.then(() => {
res.json(Data)
})
.catch(err => {
res.status(500).json(err)
});
});
I am a newbie in cloud functions. I want to create a new field counter whenever a document is created.
I tried the following codes:
exports.createCounter = functions.firestore.document('users/{userId}').onCreate((snap,
context) => {
console.log('onCreate created');
return snap.data.ref.set({counter: 0}, {merge: true})
.then(() => {
console.log("Count is created! "+userId);
})
.catch((error) => {
console.error("Counter Error writing document: ", error);
});
});
and using firebase-admin:
exports.createCounter = functions.firestore.document('users/{userId}').onCreate((snap, context) => {
const id = context.params.userId;
console.log('onCreate created');
return admin.firestore().collection('users')
.doc(id).set({counter: 0}, {merge: true})
.then(() => {
console.log("Document successfully written! "+id);
})
.catch((error) => {
console.error("Error writing document: ", error);
});
});
But both are not triggered when a new document is created.
UPDATE
At first, I create users/userId/collection/doc. When it is created, I want to add a counter field into users/userId
Update following your comment
You can add a field to the users/userId document when you create a doc under the users/userId/theCollection collection by modifying the code of the second solution presented below.
Just trigger at the level of the subcollection, get the parent document id through the context object and build its DocumentReferencebased on this id, as follows:
exports.createCounter = functions.firestore.document('users/{userId}/theCollection/{docId}').onCreate((snap,
context) => {
const id = context.params.userId;
return admin.firestore().collection('users')
.doc(id).set({ counter: 0 }, { merge: true })
.then(() => {
console.log("Count is created! " + userId);
return null;
})
.catch((error) => {
console.error("Counter Error writing document: ", error);
return null;
});
});
Initial answer
The following should do the trick:
exports.createCounter = functions.firestore.document('users/{userId}').onCreate((snap,
context) => {
console.log('onCreate created');
const docRef = snap.ref;
return docRef.set({counter: 0}, {merge: true})
.then(() => {
console.log("Count is created! "+userId);
return null;
})
.catch((error) => {
console.error("Counter Error writing document: ", error);
return null;
});
});
Note that instead of doing snap.data.ref to get the DocumentReference you have to do snap.ref. As a matter of fact, snap is a DocumentSnapshot and you need to use its ref property.
Your second code snippet should normally work, but you need to return a value in the then() and in the catch() to indicate to the Cloud Function platform that the work of the function is complete. Note that you need to do the same thing for the above code. I would suggest you watch the 3 videos about "JavaScript Promises" from the Firebase video series: https://firebase.google.com/docs/functions/video-series/ which explain this key point.
exports.createCounter = functions.firestore.document('users/{userId}').onCreate((snap, context) => {
const id = context.params.userId;
console.log('onCreate created');
return admin.firestore().collection('users')
.doc(id).set({counter: 0}, {merge: true})
.then(() => {
console.log("Document successfully written! "+id);
return null;
})
.catch((error) => {
console.error("Error writing document: ", error);
return null;
});
});
I have a collection accounts structured as follow:
Now I have a user who owns two accounts:
How do I make a query to fetch the account of this user and return it as a resolution of a promise?
Here is what I tried. It returns []
getAccounts(user_id, userRef) {
return new Promise((res, rej) => {
this.db.runTransaction((transaction) => {
return transaction.get(userRef.doc(user_id)).then((userDoc) => {
let accounts = []
if (!userDoc.exists) {
throw "User Document does not exist!";
}
let userData = userDoc.data()
let accountList = userData.accounts
for (var id in accountList){
transaction.get(this.ref.doc(id)).then(ans => {
accounts.push(ans.data())
}).catch(e => {
console.log(e)
})
}
return accounts
}).then(function (ans) {
res(ans)
}).catch((e) => {
console.log(e)
});
}).catch(function (err) {
console.error(err);
rej(err)
});
})
}
You don't need to use a transaction, since you are just reading some documents. Since you want to execute, in parallel, two (or more) asynchronous methods which return a promise (i.e. the two get() for the accounts docs) you should use Promise.all().
Something along the following lines should work:
getAccounts(user_id, userRef) {
return db.collection('users').doc(user_id).get() //replaced since I am not sure what is this.db in your case
.then(userDoc => {
const promises = []
if (!userDoc.exists) {
throw "User Document does not exist!";
}
let userData = userDoc.data()
let accountList = userData.accounts
for (var id in accountList){
promises.push(db.collection('accounts').doc(id).get())
})
return Promise.all(promises)
})
.then((results) => {
return results.map(doc => doc.data());
})
.catch(err => {
....
});
}
Note that I have used the "classic" definition for the DocumentReferences (i.e. db.collection('users').doc(user_id) and db.collection('accounts').doc(id)) since I am not 100% sure what are this.ref and this.db in your case. Just adapt as you wish!
You may also rework it with return new Promise((res, rej) => {}) as you wish but the overall philosophy remains the same.
i'm replicating this code from the firebase documentation in google cloud functions:
var cityRef = db.collection('cities').doc('SF');
var transaction = db.runTransaction(t => {
return t.get(cityRef)
.then(doc => {
// Add one person to the city population
var newPopulation = doc.data().population + 1;
t.update(cityRef, { population: newPopulation });
});
}).then(result => {
console.log('Transaction success!');
}).catch(err => {
console.log('Transaction failure:', err);
});
But i get: property 'data' does not exist on type 'QuerySnapshot' when it should be a documentSnapshot.
I found the answer after i opened an issue on github and get some insight https://github.com/firebase/firebase-admin-node/issues/344
async function setCounts(storeRef: admin.firestore.DocumentReference) { // Type here
var transaction = admin.firestore().runTransaction(t => {
return t.get(storeRef)
.then((doc: admin.firestore.DocumentSnapshot) => { // Type here
x = doc.data(); // Now i can get data() from the doc
});
}).then(result => {
console.log('Transaction success!');
}).catch(error => {
console.log('Transaction failure:', error);
});
}
I finally made it work by explicitly declaring the types for DocumentReference and DocumentSnapshot, i don't know why, but while deploying the linter inferred doc as QuerySnapshot even though it was not.