I am struggling with async operations. I am trying to simply get a value from firestore and storing it in a var.
I manage to receive the value, I can even save it in the var when I do that specifically (use the var within the get function) but I don't seem to manage the await properly when trying to save this in a flexible way:
async function getValues(collectionName, docName,) {
console.log("start")
var result;
var docRef = await db.collection(collectionName).doc(docName).get()
.then(//async// (tried this as well with async) function (doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
result = doc.data().text;
console.log(result);
return //await// (this as well with async) result;
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
result = "No such document!";
return result;
}
console.log("end");
}).catch (function (err) {
console.log('Error getting documents', err);
});
};
helpMessage = getValues('configuration','helpMessage');
Note: doc.data().text -> "text" is the name of the field where my value is stored in. Do I have to use .value here?
The result I get in the console is: info: Document data: { text: 'The correct text from the database' }
info: The correct text from the database
But using helpMessage in my code I get {}
Image from the Telegram bot where I am trying to use the helpMessage as a response to the '/help' command.
I have checked: getting value from cloud firestore,
Firebase Firestore get() async/await, get asynchronous value from firebase firestore reference and most importantly How do I return the response from an asynchronous call?. They either deal with multiple documents (using forEach), don't address the async nature of my problem or (last case), I simply fail to understand the nature of it.
Additionally, both nodejs and firestore seems to be developing rapidly and finding good, up-to-date documentation or examples is difficult. Any pointers are much appriciated.
You have things the wrong way around. It's much easier than you think it is.
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");
}};
}
If a function returns a promise (like db.collection(...).doc(...).get()), return that promise. This is the "outer" return above.
In the promise handler (inside the .then() callback), return a value to indicate success, or a rejected promise to indicate an error. This is the "inner" return above. Instead of returning a rejected promise, you can also throw an error if you want to.
Now you have a promise-returning function. You can use it with .then() and .catch():
getValues('configuration','helpMessage')
.then(function (text) { console.log(text); })
.catch(function (err) { console.log("ERROR:" err); });
or 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);
}
}
If you want to use async/await with your getValues() function, you can:
async function getValues(collectionName, docName) {
let doc = await db.collection(collectionName).doc(docName).get();
if (doc.exists) return doc.data().text;
throw new Error("No such document");
}
Since getValues function returns a promise, you need to await getValues function while calling it.
Change getValues like so -
function getValues(collectionName, docName,) {
console.log("start")
var result;
return db.collection(collectionName).doc(docName).get()
.then(function (doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
result = doc.data().text;
console.log(result);
return result;
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
result = "No such document!";
return result;
}
}).catch (function (err) {
console.log('Error getting documents', err);
});
};
Then use getValues like so -
helpMessage = await getValues('configuration','helpMessage');
Explanation -
async, await are just syntactic sugar for Promises. async functions return a promise (or AsyncFunction more accurately) which needs to be resolved to use its enclosed value.
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Finally managed to get it working. Thanks for the input Tomalak!
getValues(help.collectionName, help.docName)
.then((text) => {
console.log(text);
help.message = text;
})
.catch((err) => { console.log("Error: ", err); });
function getValues(collectionName, docName) {
return db.collection(collectionName).doc(docName).get().then((doc) => {
if (doc.exists) {
return doc.data().text;
}
else {
return Promise.reject("No such document");
}});
}
bot.help((ctx) => ctx.reply(help.message));
Unfortunately, I can not pin-point the exact reason this worked. Some little fixes (missed comma in the console.log) and formatting definitely helped me understanding the structure though. Hope someone else finds this useful, when starting to play around with node and firebase.
Related
I tried querying Firestore using .get():
//Cloud function to perform leaderboard calculation
exports.scheduledLeaderboardFunction = functions.pubsub.schedule('00 21 * * *')
.timeZone('America/Los_Angeles')
.onRun(async (context) => {
var globalPostsArray = [];
try {
await admin.firestore()
.collection('globalPosts')
.get()
.then((querySnapshot) => {
if(querySnapshot.exists) {
querySnapshot.forEach((res) => {
const {
//Fields
//Removed
} = res.data();
globalPostsArray.push({
//Fields
//Removed
});
});
}
else {
throw new Error("Data doesn't exist") <-------- This error is thrown
}
return null
})
.then(() => {
if (globalPostsArray.length > 0) {
console.log(globalPostsArray)
}
else {
throw new Error("length not greater than 0")
}
return null;
})
}
catch(error) {
console.log(error);
}
return null;
});
but in the firebase cloud log, I get the following error printed:
Error: Data doesn't exist
Which means querySnapshot doesn't exist when I use .get() (the error is thrown).
globalPosts, the collection I am querying, is NOT empty
if I can make .get() to work, that would work for my issue, since I am not waiting for updates which is what .onSnapshot() is good for.
summary: onSnapshot() worked for fetching the data from Firestore, but I can't use .then() to wait for the data so I can finish up the work. get() isn't working, but I can use .then() to wait for the collection if it does work.
How can I fix my issue?
EDIT: changed the function to this, but its still not working
//Cloud function to perform leaderboard calculation
exports.scheduledLeaderboardFunction = functions.pubsub.schedule('00 21 * * *')
.timeZone('America/Los_Angeles')
.onRun(async (context) => {
try {
await admin.firestore().collection('globalPosts').orderBy("date_created", "desc")
.get()
.then(function(querySnapshot) {
if(querySnapshot) {
querySnapshot.forEach(function(doc) {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
return null
}
else {
throw new Error("Data doesn't exist")
}
})
}
catch(error) {
console.log(error);
}
return null;
});
Proof globalPosts isn't empty:
Same error:
Error: Data doesn't exist
I think you're on the right track with .get(), but don't mix async syntax.
const shortSnapshot = await firebase.firestore()
.collection("stuff")
.where("stuff", "==", "new")
.limit(1)
.get();
if (shortSnapshot.empty) {
console.error("No stuff");
return response.sendStatus(404);
}
console.log(shortSnapshot.docs[0].data());
There is no .then for .onSnapShot - it attaches a Listener Function, and returns synchronously, with no data - it does NOT return a promise.
the anonymous function you created and passed - it begins (querySnapshot) => { is called with a querySnapshot as it's argument when the Listener is triggered. Judging by the other parts of the code, you're attempting a cloud function? As a general rule, listeners are NOT the correct approach for a Cloud Function, as these functions are intended to be short-lived.
The message you are shown is precisely what would be expected if your collection 'globalPosts' were empty - and you show nothing here to indicate that this is an error.
Removing the try, catch fixed my issue!
await admin.firestore().collection('globalPosts').orderBy("date_created", "desc")
.get()
.then(function(querySnapshot) {
if(querySnapshot) {
querySnapshot.forEach(function(doc) {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data());
});
return null
}
else {
throw new Error("Data doesn't exist")
}
})
The error of "data doesn't exist" is no longer an issue
hello guys i got confused why this not working
here is my connection js file
function getConnection(callback) {
initPool()
mysql_pool.getConnection((err, conn) => {
if (err) {
return callback(err);
}
return callback(err, conn);
});
}
function query(queryString, callback) {
getConnection((err, connection2) => {
if (connection2 != undefined) {
if (err) {
connection2.destroy();
return callback(createDataResponseObject(err, null))
}
connection2.query(queryString, function (err, rows) {
connection2.destroy();
if (!err) {
return callback(createDataResponseObject(err, rows))
}
else {
return callback(createDataResponseObject(err, null))
}
});
}
});
}
function createDataResponseObject(error, results) {
if (error) {
logger.info(error);
}
return {
error: error,
results: results === undefined ? null : results === null ? null : results
}
}
now when i acquire this connection js in my router.js below is sample code
var conn=require("./connection")
router.post('/test',async function(res,req){
var query ="select * from users"
let x= await conn.query(result);
console.log(x)
});
in connection js file i haven't use promise then why async not working in router i.e it should still work because i m using callback can anyone tell me what i m missing or what i m doing wrong. when i tried it with return promise in connection.js file it working. i know async return promise
You can only await a promise. conn.query doesn't return a promise. It has no return statement: It returns undefined.
You need to promisify your database functions.
Async/await only works with promises, it’s a syntactic sugar on top of it. I.e. you can’t use the syntax with callbacks.
Check out this link: https://javascript.info/async-await
Async only works with promises, to get things working you have to first convert all of your function to return promises. You can use Promise.promisify() to convert all callback style function to return a promise. Once it is done you can use these function with async-await.
I want to return dataSet from my handler function. However it's nested inside my promise chain. I'm attempting to use await/async but the value of data is still undefined. Thoughts on how to do this?
handler: (request, h) => {
let data: any;
connection.connect((err) => {
if (err) {
console.error("Error-------> " + err);
}
console.log("Connected as id " + connection.threadId);
connector.getAllEvents()
.then(async dataSet => {
console.log(dataSet);
data = await dataSet;
});
});
return data;
}
Err is not being thrown since logging to the console prints out the values I'm looking for.
In order to do this you would need to make handler return a Promise, and within the handler, wrap the connection.connect block with a Promise.
e.g.
handler: (request, h) => {
// wrap connector.connect(...) in a Promise
return Promise<any>((resolve, reject) => {
connection.connect(err => {
if (err) {
console.error("Error -----> ", err);
// error in connection, propagate error via reject
// and do not continue processing
return reject(err);
}
console.log("Connected as id " + connection.threadId);
connector.getAllEvents()
// don't think you need this to be async
// as connector.getAllEvents() will should return a Promise<T>
// and .then() is like a .map() so its first argument is a T
// rather than a Promise<T>
.then(dataSet => {
console.log(dataSet);
// we finally have our value
// so we propagate it via resolve()
resolve(dataSet);
});
});
});
}
Data is not initialized when you return it. You can test it by adding another log statement just before return, you'll see it prints before console.log(dataSet);
I don't know what connection.connect returns (what framework is it?), but you can promisify it. Then you either return a promise to "connect and get the data" and let the caller wait on it, or you await on it inside your function and return the data after promise is fulfilled.
I am trying to get Promise chaining working for me correctly.
I believe the problem boils down to understanding the difference between:
promise.then(foo).then(bar);
and:
promise.then(foo.then(bar));
In this situation I am writing both foo and bar and am trying to get the signatures right. bar does take a return value that is produced by foo.
I have the latter working, but my question is what do I need to do to get the former working?
Related to the above is the full code (below). I don't have the different logs printed in the order I am expecting (expecting log1, log2, log3, log4, log5, but getting log3, log4, log5, log1, log2). I am hoping as I figure the above I will get this working right as well.
var Promise = require('bluebird');
function listPages(queryUrl) {
var promise = Promise.resolve();
promise = promise
.then(parseFeed(queryUrl)
.then(function (items) {
items.forEach(function (item) {
promise = promise.then(processItem(transform(item)))
.then(function() { console.log('log1');})
.then(function() { console.log('log2');});
});
}).then(function() {console.log('log3')})
).then(function() {console.log('log4')})
.catch(function (error) {
console.log('error: ', error, error.stack);
});
return promise.then(function() {console.log('log5');});
};
What is the difference between promise.then(foo).then(bar); and promise.then(foo.then(bar));?
The second one is simply wrong. The then method takes a callback as its argument, not a promise. That callback might return a promise, so the first one is equivalent to
promise.then(function(x) { return foo(x).then(bar) })
(assuming that foo returns a promise as well).
Your whole code appears to be messed up a bit. It should probably read
function listPages(queryUrl) {
return parseFeed(queryUrl)
.then(function (items) {
var promise = Promise.resolve();
items.forEach(function (item) {
promise = promise.then(function() {
console.log('log1');
return processItem(transform(item));
}).then(function() {
console.log('log2');
});
});
return promise;
}).then(function() {
console.log('log3')
}, function (error) {
console.log('error: ', error, error.stack);
});
}
The node mongodb docs specify to use next/each for large number of documents so as to not have everything loaded onto memory if we were to use toArray.
So, i thought my sample code should work as is. But it just returns one document.
What should be the correct way to deal with this problem?
This is my code sample :
var findAsync = function (collection,query) {
return mongodb.MongoClient.connectAsync(mongodbServerString)
.then(function (db) {
return [db.collection(collection).find(query), db];
});
};
findAsync("UserProfile",{})
.spread(function (cursor,db) {
return [cursor.project({Email:true}),db];
})
.spread(function (cursor, db) {
return cursor.eachAsync().then(function (doc) {
console.log(doc);
}).catch(function () {
db.close();
});
});
Promises represent singular values. Promises are basically like function returns, since a function cannot return multiple values - it wouldn't make sense to convert each to a promise returning function.
What you can do is either convert it to an Observable returning function and then use .forEach on that to get a promise back for the completion of the sequence or you can implement something similar manually:
function each(cursor, fn) {
return new Promise((resolve, reject) => {
cursor.forEach((err, data) => {
if(err) {
cursor.close();
return reject(err);
}
try { fn(data); } catch(e) { cursor.close(); reject(e); }
}, err => { { // finished callback
if(err) reject(err);
else resolve();
});
});
}
Which would let you write:
each(cursor, doc => console.log(doc)).then(...).catch(...)
Also note that Mongo connections are persistent, you're supposed to connect once when the server starts and then keep the connection open for as long as the server is run.