Guys I need some help with a get return with node and firebase.
I get an empty array.
but in console.log inside foreach prints correct
[{cnpj: '03052164798-000179', Address: 'Av Duke of Caxias 99999', Name: 'Testing', Tel: '999999999'}]
getEmpresas() {
let empresas = []
firebase.firestore().collection('empresa').get().then(snapshot => {
snapshot.docs.forEach(empresa => {
empresas.push(empresa.data())
console.log(empresas)
});
})
return empresas
I think this is a question about async. You're not getting the data you expect in the return because that fires before the async request is made. You'd need something like:
const getEmpresas = async () => {
let empresas = []
const snapshot = await firebase
.firestore()
.collection('empresa')
.get();
snapshot.docs.forEach((empresa) => {
empresas.push(empresa.data());
});
return empresas;
};
As imjared said in his answer, the function returns before .get() gets executed because it is an asynchronous function
You will need to return the chain of firebase functions which will return a Promise
getEmpresas() {
return firebase.firestore().collection('empresa').get().then(snapshot => (
snapshot.docs.map(({ data }) => data())
))
}
Then to access the returned Promise use .then() on the Promise or await inside an async function
getEmpresas.then(empresas => console.log(empresas))
Or inside an async function
const main = async () => {
const empresas = await getEmpresas()
console.log(empresas)
}
main()
Related
Building a NodeJS REST API.
Trying to send load data from FireBase collection, then sending it to the user (as API response).
Looks like the problem is that it's not waits for the firebase fetch to resolve, but send back a response without the collection data. (tried to use ASYNC-AWAIT but its not working)
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {
snapshot.docs.forEach(msg => {
console.log(msg.data().messageContent)
return {
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}
})
})
}
try {
const chatData = await getChatData()
console.log(chatData)
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData
})
} catch (err) {
if (!err.statusCode) {
err.statusCode(500)
}
next(err)
}
}
As you can see, I've used 2 console.logs to realize what the problem, Terminal logs looks like:
[] (from console.logs(chatData))
All messages (from console.log(msg.data().messageContent))
Is there any way to block the code unti the firebase data realy fetched?
If I correctly understand, you want to send back an array of all the documents present in the messages subcollection. The following should do the trick.
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId;
const collectionRef = db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc');
try {
const chatsQuerySnapshot = await collectionRef.get();
const chatData = [];
chatsQuerySnapshot.forEach((msg) => {
console.log(msg.data().messageContent);
chatData.push({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
});
});
console.log(chatData);
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData,
});
} catch (err) {
if (!err.statusCode) {
err.statusCode(500);
}
next(err);
}
};
The asynchronous get() method returns a QuerySnapshot on which you can call forEach() for enumerating all of the documents in the QuerySnapshot.
You can only await a Promise. Currently, getChatData() does not return a Promise, so awaiting it is pointless. You are trying to await a fixed value, so it resolves immediately and jumps to the next line. console.log(chatData) happens. Then, later, your (snapshot) => callback happens, but too late.
const getChatData = () => new Promise(resolve => { // Return a Promise, so it can be awaited
db.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot(resolve) // Equivalent to .onSnapshot((snapshot) => resolve(snapshot))
})
const snapshot = await getChatData();
console.log(snapshot)
// Put your transform logic out of the function that calls the DB. A function should only do one thing if possible : call or transform, not both.
const chatData = snapshot.map(msg => ({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}));
res.status(200).json({
message: 'Chat Has Found',
chatData
})
Right now, getChatData is this (short version):
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {}) // some things inside
}
What that means is that the getChatData function calls some db query, and then returns void (nothing). I bet you'd want to return the db call (hopefully it's a Promise), so that your await does some work for you. Something along the lines of:
const getChatData = async () =>
db
.collection('chats')
// ...
Which is the same as const getChatData = async() => { return db... }
Update: Now that I've reviewed the docs once again, I see that you use onSnapshot, which is meant for updates and can fire multiple times. The first call actually makes a request, but then continues to listen on those updates. Since that seems like a regular request-response, and you want it to happen only once - use .get() docs instead of .onSnapshot(). Otherwise those listeners would stay there and cause troubles. .get() returns a Promise, so the sample fix that I've mentioned above would work perfectly and you don't need to change other pieces of the code.
I currently have the following code
router.get('/uri', (request,response) => {
let final = [];
TP.find({userID: request.userID})
.then(tests =>{
tests.forEach(test => {
A.findById(test.assignmentID)
.then(assignment => {
final.push({
testID: test._id,
name: assignment.title,
successRate: `${test.passedTests}/${test.totalTests}`
})
})
.catch(error => {
console.log(error)
})
})
return response.send(final);
})
.catch(err => {
console.log(err);
return response.sendStatus(500);
})
})
The code is supposed to query 2 MongoDB databases and construct an array of objects with specific information which will be sent to the client.
However, I always get an empty array when I call that endpoint.
I have tried making the functions async and make them wait for results of the nested functions but without success - still an empty array.
Any suggestions are appreciated!
forEach doesn't care about promises inside it. Either use for..of loop or change it to promise.all. The above code can be simplified as
router.get('/uri', async (request,response) => {
const tests = await TP.find({userID: request.userID});
const final = await Promise.all(tests.map(async test => {
const assignment = await A.findById(test.assignmentID);
return {
testID: test._id,
name: assignment.title,
successRate: `${test.passedTests}/${test.totalTests}`
};
}));
return response.send(final);
});
Hope this helps.
I am trying to return coordinates of given adress with gooogle maps geocoding API and fetch. I am able to log these coordinates inside my get fuction, but I have no idea how to return them from the function to use it somewhere else in the code. Already tried multiple varaitions of two approaches:
function getCoordinates1(name) {
locObj = fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${name}&key=mykey`).then( (res) => res.json()).then( (data) =>
{
console.log(data.results[0].geometry.location);
return data.results[0].geometry.location;
}).then((res) => res);
}
let coordinates1 = getCoordinates1(latinaze(name2));
console.log(coordinates1);
async function getCoordinates2(name) {
locObj = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${name}&key=mykeyk`).then( (res) => res.json()).then( (data) =>
{
console.log(data.results[0].geometry.location);
//return data.results[0].geometry.location;
}).then((res) => res);
return locObj
}
let coordinates2 = await getCoordinates2(latinaze(name2));
console.log(coordinates2);
First function returns undefined, second returns pending promise. What am I doing wrong?
The first function returns undefined because you don't return anything. As simple as that ;)
The second function returns a pending promise because you don't wait for the promise to be resolved. The promise gets resolved when the callback inside then is invoked, but that happens after you return locObj in getCoordinates2.
You should try this:
// function definition
async function getCoordinates3(name) {
const resp = await fetch(`https://maps.googleapis.com/maps/api/geocode/json?address=${name}&key=mykeyk`)
const data = await resp.json();
return data.results[0].geometry.location;
}
// usage
const coordinates3 = await getCoordinates3(latinaze(name3));
I couldn't return any value from function, so I made it a class method and set a property in function body. Now I can get the value form that property after I call the function:
export default class SearchModel {
constructor() {
this.start = '';
this.meta = '';
this.coors = [];
this.address = 'none';
}
//translate coordinates to address
async getAdress(coordinates) {
try {
let geocodeCoordinates = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${this.coors[0]},${this.coors[1]}&key=${process.env.API_GM_KEY}`
const rawData = await fetch(geocodeCoordinates);
//console.log(await rawData.json());
return await rawData.json();
} catch (error) {
return new Error(`Wild ERROR occured, can't get LocObj. Details: ${error}`);
} }
async displayAdress(coordinates) {
const data = await this.getAdress(coordinates);
const dataAdress = await data.results[0].formatted_address;
this.address = await dataAdress; }
}
I would like to have a configuration file with variables set with data I fetch from an API.
I think I must use async and await features to do so, otherwise my variable would stay undefined.
But I don't know how to integrate this and keep the node exports.myVariable = myData available within an async function ?
Below is the code I tried to write to do so (all in the same file) :
const fetchAPI = function(jsonQuery) {
return new Promise(function (resolve, reject) {
var reqOptions = {
headers: apiHeaders,
json:jsonQuery,
}
request.post(apiURL, function (error, res, body) {
if (!error && res.statusCode == 200) {
resolve(body);
} else {
reject(error);
}
});
});
}
var wallsData = {}
const fetchWalls = async function (){
var jsonQuery = [{ "recordType": "page","query": "pageTemplate = 1011"}]
let body = await utils.fetchAPI(jsonQuery)
let pageList = await body[0].dataHashes
for(i=0;i<pageList.length;i++){
var page = pageList[i]
wallsData[page.title.fr] = [page.difficultyList,page.wallType]
}
return wallsData
throw new Error("WOOPS")
}
try{
const wallsData = fetchWalls()
console.log(wallsData)
exports.wallsData = wallsData
}catch(err){
console.log(err)
}
The output of console.log(wallsData) shows Promise { <pending> }, therefore it is not resolved and the configuration file keep being executed without the data in wallsData...
What do I miss ?
Thanks,
Cheers
A promise is a special object that either succeeds with a result or fails with a rejection. The async-await-syntax is syntactic sugar to help to deal with promises.
If you define a function as aync it always will return a promise.
Even a function like that reads like
const foo = async() => {
return "hello";
}
returns a promise of a string, not only a string. And you need to wait until it's been resolved or rejected.
It's analogue to:
const foo = async() => {
return Promise.resolve("Hello");
}
or:
const foo = async() => {
return new Promise(resolve => resolve("Hello"));
}
Your fetchWalls similarly is a promise that will remain pending for a time. You'll have to make sure it either succeeds or fails by setting up the then or catch handlers in your outer scope:
fetchWalls()
.then(console.log)
.catch(console.error);
The outer scope is never async, so you cannot use await there. You can only use await inside other async functions.
I would also not use your try-catch for that outer scope promise handling. I think you are confusing the try-catch approach that is intended to be used within async functions, as there it helps to avoid nesting and reads like synchronous code:
E.g. you could do inside your fetchWalls defintion:
const fetchWalls = async function (){
var jsonQuery = [{ "recordType": "page","query": "pageTemplate = 1011"}]
try {
let body = await utils.fetchAPI(jsonQuery)
} catch(e) {
// e is the reason of the promise rejection if you want to decide what to do based on it. If you would not catch it, the rejection would chain through to the first error handler.
}
...
}
Can you change the statements like,
try{
const wallsData = fetchWalls();
wallsData.then((result) => {
console.log(result);
});
exports.wallsData = wallsData; // when importing in other file this returns as promise and we should use async/await to handle this.
}catch(err){
console.log(err)
}
I am having some issues with Async/Await when trying to process my MSSQL Query.
The query works fine because if i do
console.log(sqltest.getNextId("A"))
I get the expected result. But i need to wait for the result from first query to pass on to second one so i tried below. Am i missing here something ? I would like to keep it as clean as possible because i might have the need to execute more then 2 in Sequence.
async function someMethod() {
var newId = await sqltest.getNextId("A")
var nextId = await sqltest.updateId("A",newId)
console.log(newId + ' - ' + nextId)
}
someMethod()
Here is what my getNextId looks like
const getNextId = (counter_id) =>
{const params = [{ name: "p_counter_id", type: sql.VarChar(10), value: counter_id }]
sqlUtil
.storedProcedure(params, "sp_counter_sel")
.then(result =>
{
var newCounter = sequence
(
result.recordset[0].next_id,
result.recordset[0].counter_length,
result.recordset[0].counter_fill
)
console.log(newCounter)
return newCounter
}
)
.catch(err => {
console.log('Error: ' + err.message)
})
}
ok to eliminate all confusion here is my .storedProcedure code
let storedProcedure = async (params, storedProcedureName) => {
const pool = await getOrCreatePool()
let request = await pool.request()
params.forEach((parameter) => {
parameterDirection = parameter.isOutput ? 'output' : 'input';
request = request[parameterDirection](parameter.name, parameter.type, parameter.value)
})
try {
return await request.execute(storedProcedureName)
sql.on('error', err => {
})
} catch(err) {
let message = {
message: {
msg: err.message,
number: err.number
},
}
throw message;
}
}
There is no underhanded error it just returns undefined - undefined for the console.log
This comment is actually is the important bit!
getNextId does not actually return anything, not the value you are looking for, and not a promise that you can await. The reason you are seeing output is that getNextId runs a console.log in the then callback.
But console.log(sqltest.getNextId("A")) is probably spitting out an undefined
Since sqlUtil.storedProcedure seems to return a promise (you can tell because you call then() on it, you should be able to convert this to an async function, and await on that.
const getNextId = async (counter_id) => {
const params = [{
name: "p_counter_id",
type: sql.VarChar(10),
value: counter_id
}]
const result = await sqlUtil.storedProcedure(params, "sp_counter_sel")
var newCounter = sequence(
result.recordset[0].next_id,
result.recordset[0].counter_length,
result.recordset[0].counter_fill
)
return newCounter
}
console.log(await getNextId("A")); // Should be what you expect.
This function now has a return value. Because it is now async and has a return statement that is not nested inside another function. It now returns a Promise that resolves to newCounter, and can be called with await.