Firebase: Transaction with async/await - javascript

I'm trying to use async/await with transaction.
But getting error "Argument "updateFunction" is not a valid function."
var docRef = admin.firestore().collection("docs").doc(docId);
let transaction = admin.firestore().runTransaction();
let doc = await transaction.get(docRef);
if (!doc.exists) {throw ("doc not found");}
var newLikes = doc.data().likes + 1;
await transaction.update(docRef, { likes: newLikes });

The above did not work for me and resulted in this error: "[Error: Every document read in a transaction must also be written.]".
The below code makes use of async/await and works fine.
try{
await db.runTransaction(async transaction => {
const doc = await transaction.get(ref);
if(!doc.exists){
throw "Document does not exist";
}
const newCount = doc.data().count + 1;
transaction.update(ref, {
count: newCount,
});
})
} catch(e){
console.log('transaction failed', e);
}

If you look at the docs you see that the function passed to runTransaction is a function returning a promise (the result of transaction.get().then()). Since an async function is just a function returning a promise you might as well write db.runTransaction(async transaction => {})
You only need to return something from this function if you want to pass data out of the transaction. For example if you only perform updates you won't return anything. Also note that the update function returns the transaction itself so you can chain them:
try {
await db.runTransaction(async transaction => {
transaction
.update(
db.collection("col1").doc(id1),
dataFor1
)
.update(
db.collection("col2").doc(id2),
dataFor2
);
});
} catch (err) {
throw new Error(`Failed transaction: ${err.message}`);
}

IMPORTANT: As noted by a couple of the users, this solution doesn't use the transaction properly. It just gets the doc using a transaction, but the update runs outside of it.
Check alsky's answer. https://stackoverflow.com/a/52452831/683157
Take a look to the documentation, runTransaction must receive the updateFunction function as parameter. (https://firebase.google.com/docs/reference/js/firebase.firestore.Firestore#runTransaction)
Try this
var docRef = admin.firestore().collection("docs").doc(docId);
let doc = await admin.firestore().runTransaction(t => t.get(docRef));
if (!doc.exists) {throw ("doc not found");}
var newLikes = doc.data().likes + 1;
await doc.ref.update({ likes: newLikes });

In my case, the only way I could get to run my transaction was:
const firestore = admin.firestore();
const txRes = await firestore.runTransaction(async (tx) => {
const docRef = await tx.get( firestore.collection('posts').doc( context.params.postId ) );
if(!docRef.exists) {
throw new Error('Error - onWrite: docRef does not exist');
}
const totalComments = docRef.data().comments + 1;
return tx.update(docRef.ref, { comments: totalComments }, {});
});
I needed to add my 'collection().doc()' to tx.get directly and when calling tx.update, I needed to apply 'docRef.ref', without '.ref' was not working...

Related

toObject is not a function error in mongoose

This is the filesPost controllers file. Here I fetch all the datas from MongoDB as well I push datas there. The function works very well without logging in console the userInfo but when I try to log it in console, it gives the error that userInfo.toObject({getters: true}) is not a function. I have tried toJSON() but I got the same error.
const { validationResult } = require("express-validator");
const HttpError = require("../models/http-error");
const File = require("../models/file");
const User = require("../models/user");
const getFilesByUserId = async (req, res, next) => {
const userId = req.params.uid;
let filePosts;
try {
filePosts = await File.find({ creator: userId });
} catch (err) {
return next(new HttpError("Fetching failed, please try again.", 500));
}
const userInfo = User.findById(userId);
if (!filePosts || filePosts.length === 0) {
return next(
new HttpError("Could not find files for the provided user id.", 404)
);
}
console.log(userInfo.toObject({ getters: true }));
res.json({
filePosts: filePosts.map((file) => file.toObject({ getters: true })),
});
};
In your async function, your code continues even though the call to User.findById(userId) isn't complete. You can use an await statement to ensure that the function has run before your code continues.
Your code should work if you change const userInfo = User.findById(userId); to
const userInfo = await User.findById(userId);
For more information, here is the async/await documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Just needed to add await.
const userInfo = await User.findById(userId);

Handling Async Await to use Firebase with react native

const userID = firebase.auth().currentUser.uid
const checkRoomExists = async ()=>{
var queryRef = firestore.collection("groups");
try{
console.log("userId: "+userID)
var snapshot = await queryRef.where('participant', '==',userID).where('host','==',findParticipant).get()
if (snapshot.empty) {
console.log('No matching documents1.');
return;
}
else{
snapshot.forEach(doc => {
setChatroomAlready(doc.id)
})}
}
catch(e){
console.log('Error getting documents', e);
}
finally{
console.log("chatroomAlready :"+chatroomAlready)
}
}
async function performCreateGroup () {
console.log("1 :"+findParticipant)
checkRoomExists();
console.log("2 :"+chatroomAlready)
}
//first call
1: ZUxSZP09fzRzndr7vhK8wr56j3J3
2: (blank)
//second call returns what I expected exactly
1: ZUxSZP09fzRzndr7vhK8wr56j3J3
2: zQN4hbgqjKhHHHc70hPn
This is my code and variable chatroomAlready returns ''
But if I fast-refresh my app with react-native's function, It returns the expected value very well
I think this problem happens because my less understanding of async-await.
Any helps or clues can I solve this problem?
i would rather use the following syntaxe:
queryRef.where('participant', '==',userID).where('host','==',findParticipant).get().then((snapshot)=>(
DO YOUR STUFF))
Since queryRef act as a promise.

Unable to return a value from an async function using firestore

I am new to async and am trying to return a value from a Firestore db using node.
The code does not produce any errors, nor does it produce any results!
I want to read the db, get the first match and return this to the var country.
const {Firestore} = require('#google-cloud/firestore');
const db = new Firestore();
async function getCountry() {
let collectionRef = db.collection('groups');
collectionRef.where('name', '==', 'Australia').get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
return "Hello World";
}
const docRef = snapshot.docs[0];
return docRef;
})
.catch(err => {
console.log('Error getting documents', err);
});
}
let country = getCountry();
When you declare an function async, that means it always returns a promise. It's generally expected that the code inside it will use await to deal with other promises generated within that function. The final returned promise will resolve with the value returned by the function.
First of all, your async function should look more like this:
async function getCountry() {
let collectionRef = db.collection('groups');
const snapshot = await collectionRef.where('name', '==', 'Australia').get()
if (snapshot.empty) {
console.log('No matching documents.');
// you might want to reconsider this value
return "Hello World";
}
else {
return snapshot.docs[0];
})
}
Since it returns a promise, you would invoke it like any other function that returns a promise:
try {
let country = await getCountry();
}
catch (error) {
console.error(...)
}
If you can't use await in the context of your call to getCountry(), you will have to handle it normally:
getCountry()
.then(country => {
console.log(country);
})
.catch(error => {
console.error(...)
})
The moment you sign up to use async/await instead of then/catch, things become much different. I suggest reading up more on how it works.

NodeJS Express app await is only valid in async function but this clearly is async function?

I have made a function to check if a certain thing already exists in the database. I have simply copy-pasted the logic I'm using to get something in the database and changed the query object + what is returned. But now it seems that node doesn't like that and just throws an error that makes no sense to me.
Where I call the function:
let exists = await queryDatabaseExists(uniqueQuery, res);
The function that I'm calling:
async function queryDatabaseExists(queryParam, res) {
try {
const cp = new sql.ConnectionPool(config);
await cp.connect();
let result = await cp.request().query(queryParam);
if(result.recordset.rowsAffected[0] = 1){return true} else { return false }
} catch (err) {
res.status(520).send(`Database error: ${err}`);
}
}
The error that I'm getting:
let exists = await queryDatabaseExists(uniqueQuery, res);
^^^^^
SyntaxError: await is only valid in async function
ALL code for that route:
router.post("/admin/category", (req, res) => {
uniqueQuery = `SELECT [name] from [dbo].[idtTV_categories] WHERE [name] = '${req.body.name}'`
getQuery = `SELECT [id]
,[name]
,[description]
,[created_time]
,[created_by] from [dbo].[idtTV_categories]`
standardQuery = `INSERT INTO [dbo].[idtTV_categories] ([name],[description],[created_time],[created_by])
VALUES
('${req.body.name}',
'${req.body.description}',
SYSDATETIME(),
'${req.user.name}')`;
let exists = checkIfExists();
function checkIfExists() { result = await queryDatabaseExists(uniqueQuery, res); return result} ;
console.log(exists);
if(req.user.roles.some(role => role === admin || role === editor)){
if(!existsInDatabase){
if(queryDatabase(standardQuery, res)){queryDatabase_get(getQuery, res)}
}
}
else { res.statusMessage = `${req.user.name} is not authorized to add categories.`;
console.log(req.user.roles)
res.status(520).send() };
})
All functions being called:
///////////// MAIN QUERYING FUNCTION //////////////////////
async function queryDatabase_get(queryParam, res) {
try {
const cp = new sql.ConnectionPool(config);
await cp.connect();
let result = await cp.request().query(queryParam);
res.send(result.recordset);
} catch (err) {
res.status(520).send(`Database error: ${err}`);
}
}
async function queryDatabaseExists(queryParam, res) {
try {
const cp = new sql.ConnectionPool(config);
await cp.connect();
let result = await cp.request().query(queryParam);
if(result.recordset.rowsAffected[0] = 1){return true} else { return false }
} catch (err) {
res.status(520).send();
}
}
async function queryDatabase(queryParam, res) {
try {
const cp = new sql.ConnectionPool(config);
await cp.connect();
let result = await cp.request().query(queryParam);
if(result.rowsAffected > 0){ return true }
} catch (err) {
res.status(520).send(`Database error: ${err}`);
}
}
it must be inside async.
ex:
app.post('/', async (req, res) => {
let exists = await queryDatabaseExists(uniqueQuery, res);
});
This means that the function in which the call to queryDatabaseExists is performed must also be async, in order to use the await keyword inside it. The queryDatabaseExists function itself looks correct.
queryDatabaseExists function need to return promise if not you cannot use await
await command expect a promise to be return by the function link
let exists = await queryDatabaseExists(uniqueQuery, res);
await can only be used in async function. For your scenario you wanted wait for result from uniquequery. First you have to make change in your router.post callback like router.post('/url',async(req,res)=>{}); for making a synchornus call to checkifexist function. Second in order to use await in checkifexist function you have to make changes to checkisexist function to async function checkifexist(){}. Thirdly you wanted to wait for DB response for that you have use await while calling checkifexist function --> let result=await checkifexist(). you can check MDN website for better understanding.
router.post('url',async(req,res)=>{// in order to use await in checkifexist
//your rest of the code.
let result=await checkifexist();// waiting for db result
async function checkifexist(){//your awaited code.}
console.log(result);
});

Get Knex.js transactions working with ES7 async/await

I'm trying to couple ES7's async/await with knex.js transactions.
Although I can easily play around with non-transactional code, I'm struggling to get transactions working properly using the aforementioned async/await structure.
I'm using this module to simulate async/await
Here's what I currently have:
Non-transactional version:
works fine but is not transactional
app.js
// assume `db` is a knex instance
app.post("/user", async((req, res) => {
const data = {
idUser: 1,
name: "FooBar"
}
try {
const result = await(user.insert(db, data));
res.json(result);
} catch (err) {
res.status(500).json(err);
}
}));
user.js
insert: async (function(db, data) {
// there's no need for this extra call but I'm including it
// to see example of deeper call stacks if this is answered
const idUser = await(this.insertData(db, data));
return {
idUser: idUser
}
}),
insertData: async(function(db, data) {
// if any of the following 2 fails I should be rolling back
const id = await(this.setId(db, idCustomer, data));
const idCustomer = await(this.setData(db, id, data));
return {
idCustomer: idCustomer
}
}),
// DB Functions (wrapped in Promises)
setId: function(db, data) {
return new Promise(function (resolve, reject) {
db.insert(data)
.into("ids")
.then((result) => resolve(result)
.catch((err) => reject(err));
});
},
setData: function(db, id, data) {
data.id = id;
return new Promise(function (resolve, reject) {
db.insert(data)
.into("customers")
.then((result) => resolve(result)
.catch((err) => reject(err));
});
}
Attempt to make it transactional
user.js
// Start transaction from this call
insert: async (function(db, data) {
const trx = await(knex.transaction());
const idCustomer = await(user.insertData(trx, data));
return {
idCustomer: idCustomer
}
}),
it seems that await(knex.transaction()) returns this error:
[TypeError: container is not a function]
I couldn't find a solid answer for this anywhere (with rollbacks and commits) so here's my solution.
First you need to "Promisify" the knex.transaction function. There are libraries for this, but for a quick example I did this:
const promisify = (fn) => new Promise((resolve, reject) => fn(resolve));
This example creates a blog post and a comment, and rolls back both if there's an error with either.
const trx = await promisify(db.transaction);
try {
const postId = await trx('blog_posts')
.insert({ title, body })
.returning('id'); // returns an array of ids
const commentId = await trx('comments')
.insert({ post_id: postId[0], message })
.returning('id');
await trx.commit();
} catch (e) {
await trx.rollback();
}
Here is a way to write transactions in async / await.
It is working fine for MySQL.
const trx = await db.transaction();
try {
const catIds = await trx('catalogues').insert({name: 'Old Books'});
const bookIds = await trx('books').insert({catId: catIds[0], title: 'Canterbury Tales' });
await trx.commit();
} catch (error) {
await trx.rollback(error);
}
Async/await is based around promises, so it looks like you'd just need to wrap all the knex methods to return "promise compatible" objects.
Here is a description on how you can convert arbitrary functions to work with promises, so they can work with async/await:
Trying to understand how promisification works with BlueBird
Essentially you want to do this:
var transaction = knex.transaction;
knex.transaction = function(callback){ return knex.transaction(callback); }
This is because "async/await requires the either a function with a single callback argument, or a promise", whereas knex.transaction looks like this:
function transaction(container, config) {
return client.transaction(container, config);
}
Alternatively, you can create a new async function and use it like this:
async function transaction() {
return new Promise(function(resolve, reject){
knex.transaction(function(error, result){
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
// Start transaction from this call
insert: async (function(db, data) {
const trx = await(transaction());
const idCustomer = await(person.insertData(trx, authUser, data));
return {
idCustomer: idCustomer
}
})
This may be useful too: Knex Transaction with Promises
(Also note, I'm not familiar with knex's API, so not sure what the params are passed to knex.transaction, the above ones are just for example).
For those who come in 2019.
After I updated Knex to version 0.16.5. sf77's answer doesn't work anymore due to the change in Knex's transaction function:
transaction(container, config) {
const trx = this.client.transaction(container, config);
trx.userParams = this.userParams;
return trx;
}
Solution
Keep sf77's promisify function:
const promisify = (fn) => new Promise((resolve, reject) => fn(resolve));
Update trx
from
const trx = await promisify(db.transaction);
to
const trx = await promisify(db.transaction.bind(db));
I think I have found a more elegant solution to the problem.
Borrowing from the knex Transaction docs, I will contrast their promise-style with the async/await-style that worked for me.
Promise Style
var Promise = require('bluebird');
// Using trx as a transaction object:
knex.transaction(function(trx) {
var books = [
{title: 'Canterbury Tales'},
{title: 'Moby Dick'},
{title: 'Hamlet'}
];
knex.insert({name: 'Old Books'}, 'id')
.into('catalogues')
.transacting(trx)
.then(function(ids) {
return Promise.map(books, function(book) {
book.catalogue_id = ids[0];
// Some validation could take place here.
return knex.insert(book).into('books').transacting(trx);
});
})
.then(trx.commit)
.catch(trx.rollback);
})
.then(function(inserts) {
console.log(inserts.length + ' new books saved.');
})
.catch(function(error) {
// If we get here, that means that neither the 'Old Books' catalogues insert,
// nor any of the books inserts will have taken place.
console.error(error);
});
async/await style
var Promise = require('bluebird'); // import Promise.map()
// assuming knex.transaction() is being called within an async function
const inserts = await knex.transaction(async function(trx) {
var books = [
{title: 'Canterbury Tales'},
{title: 'Moby Dick'},
{title: 'Hamlet'}
];
const ids = await knex.insert({name: 'Old Books'}, 'id')
.into('catalogues')
.transacting(trx);
const inserts = await Promise.map(books, function(book) {
book.catalogue_id = ids[0];
// Some validation could take place here.
return knex.insert(book).into('books').transacting(trx);
});
})
await trx.commit(inserts); // whatever gets passed to trx.commit() is what the knex.transaction() promise resolves to.
})
The docs state:
Throwing an error directly from the transaction handler function automatically rolls back the transaction, same as returning a rejected promise.
It seems that the transaction callback function is expected to return either nothing or a Promise. Declaring the callback as an async function means that it returns a Promise.
One advantage of this style is that you don't have to call the rollback manually. Returning a rejected Promise will trigger the rollback automatically.
Make sure to pass any results you want to use elsewhere to the final trx.commit() call.
I have tested this pattern in my own work and it works as expected.
Adding to sf77's excellent answer, I implemented this pattern in TypeScript for adding a new user where you need to do the following in 1 transaction:
creating a user record in the USER table
creating a login record in the LOGIN table
public async addUser(user: User, hash: string): Promise<User> {
//transform knex transaction such that can be used with async-await
const promisify = (fn: any) => new Promise((resolve, reject) => fn(resolve));
const trx: knex.Transaction = <knex.Transaction> await promisify(db.transaction);
try {
let users: User [] = await trx
.insert({
name: user.name,
email: user.email,
joined: new Date()})
.into(config.DB_TABLE_USER)
.returning("*")
await trx
.insert({
email: user.email,
hash
}).into(config.DB_TABLE_LOGIN)
.returning("email")
await trx.commit();
return Promise.resolve(users[0]);
}
catch(error) {
await trx.rollback;
return Promise.reject("Error adding user: " + error)
}
}

Categories