Facing issue with scooping in node JS - node.js

This code is showing empty object ( {} )
// declared at top
let mainData = {};
let trainStations = {};
let routes = {};
let trainNo = {};
data["data"].forEach(async (element) => {
const response2 = await fetch(
`https://india-rail.herokuapp.com/trains/getRoute?trainNo=${element["train_base"]["train_no"]}`
);
const data2 = await response2.json();
data2["data"].forEach((ele) => {
routes[ele["source_stn_code"]] = true;
});
trainNo[element["train_base"]["train_no"]] = routes;
});
console.log(trainNo);
if i do this then i will give response with data
data["data"].forEach(async (element) => {
const response2 = await fetch(
`https://india-rail.herokuapp.com/trains/getRoute?trainNo=${element["train_base"]["train_no"]}`
);
const data2 = await response2.json();
data2["data"].forEach((ele) => {
routes[ele["source_stn_code"]] = true;
});
trainNo[element["train_base"]["train_no"]] = routes;
console.log(trainNo);
});
maybe there is some scooping issue please kindly help me to solve this problem :)

Please refer here and also check this.
As a short note, using await inside a forEach() loop will give unexpected results. This is because the forEach() does not wait until the promise to settled (either fulfilled or rejected).
A simple solution for this could be using either the traditional for loop or the for..of loop.
for(let element of data["data"]){
const response2 = await fetch(
`https://india-rail.herokuapp.com/trains/getRoute?trainNo=${element["train_base"]["train_no"]}`
);
const data2 = await response2.json();
data2["data"].forEach((ele) => {
routes[ele["source_stn_code"]] = true;
});
trainNo[element["train_base"]["train_no"]] = routes;
}
console.log(trainNo);
NOTE: Make sure to wrap the above for..of loop inside an async function because the await keyword is allowed inside a function only when the function is defined with async keyword.

Related

'Unexpected token' when recursively calling async function in Nodejs

My app contains posts with nested comments in Firebase Firestore structured such that each post/comment with docID has a sub collection postComments. Thus, a given post/comment can have an infinite number of nested comments.
comments
- docID
postComments
- docID
- docID
- docID
- docID
postComments
- docID
- docID
I am currently writing a Firebase cloud function to recursively query all documents and sub collection documents of a given docID and return all of those documents in an array. My plan was to define the getChildComments async function which takes in a docID and returns all of the documents in that document's postComments sub collection. I would then recursively call getChildComments until I have built an array with all of the nested comments in a thread.
exports.loadWholeCommentThread = functions.https.onCall(async (data, context) => {
let comments = await getChildComments(data.rootID);
return comments;
});
async function getChildComments(docID) {
try {
const db = admin.firestore();
const commentsRef = db.collection('comments').doc(docID).collection('postComments');
var comments = [];
const commentsQuerySnap = await commentsRef.get();
commentsQuerySnap.forEach((comment) => {
let commentData = comment.data();
comments.push(commentData);
if (commentData.commentCount > 0) {
let childComments = await getChildComments(commentData.commentID);
comments.concat(childComments);
}
});
return comments;
} catch (error) {
functions.logger.log(error);
throw new functions.https.HttpsError('unknown', 'ERROR0', { message: error.message } )
}
}
Unfortunately, when I try to deploy my code, I get the error Parsing error. Unexpected token getChildComments on the line where I recursively call getChildComments inside of getChildComments. Removing the await from this line fixes the build issue but then the recursive call doesn't finish.
How should I fix my issue? Or is there a better way to query all nested documents?
This is because you have used await outside of an async function (note that it is inside an arrow function!).
const comments = [];
const commentsQuerySnap = await commentsRef.get();
commentsQuerySnap.forEach((comment) => {
let commentData = comment.data();
comments.push(commentData);
if (commentData.commentCount > 0) {
let childComments = await getChildComments(commentData.commentID); // the keyword "await" here is invalid
comments = comments.concat(childComments);
}
});
But you can't just add async to this arrow function, because then your code won't properly wait for the comments array to be filled.
To properly fix this, you need to use .map() on the commentsQuerySnap.docs array in addition to using Promise.all to wait for each comment (and its children) to be retrieved.
const comments = [];
const commentsQuerySnap = await commentsRef.get();
await Promise.all(
commentsQuerySnap.docs.map(
async (comment) => {
let commentData = comment.data();
comments.push(commentData);
if (commentData.commentCount > 0) {
let childComments = await getChildComments(commentData.commentID);
comments = comments.concat(childComments);
}
})
)
);
While that above block works, the comments array may be out of order to what you were expecting. If you must maintain order of the comments fetched so they are in the same order as the query, you should return the built comments array for each document and then flatten them after they all have been retrieved.
// removed const comments = []; here
const commentsQuerySnap = await commentsRef.get();
const arrayOfCommentThreads = await Promise.all(
commentsQuerySnap.docs.map(
async (comment) => {
let commentData = comment.data();
const commentThread = [commentData];
if (commentData.commentCount > 0) {
let childComments = await getChildComments(commentData.commentID);
commentThread = commentThread.concat(childComments);
}
return commentThread;
})
)
);
const comments = arrayOfCommentThreads.flat();
Personally, I prefer the spread operator to using .concat like so:
const commentsQuerySnap = await commentsRef.get();
const arrayOfCommentThreads = await Promise.all(
commentsQuerySnap.docs.map(
async (comment) => {
const commentData = comment.data();
if (commentData.commentCount === 0) {
return [commentData];
}
const childComments = await getChildComments(commentData.commentID);
return [commentData, ...childComments];
})
)
);
const comments = arrayOfCommentThreads.flat();

Firebase cloud function: http function returns null

Here is what I am trying to do.
I am introducing functionality to enable users to search for local restaurants.
I created a HTTP cloud function, so that when the client delivers a keyword, the function will call an external API to search for the keyword, fetch the responses, and deliver the results.
In doing #2, I need to make two separate url requests and merge the results.
When I checked, the function does call the API, fetch the results and merge them without any issue. However, for some reason, it only returns null to the client.
Below is the code: could someone take a look and advise me on where I went wrong?
exports.restaurantSearch = functions.https.onCall((data,context)=>{
const request = data.request;
const k = encodeURIComponent(request);
const url1 = "an_url_to_call_the_external_API"+k;
const url2 = "another_url_to_call_the_external_API"+k;
const url_array = [ url1, url2 ];
const result_array = [];
const info_array = [];
url_array.forEach(url=>{
return fetch(url, {headers: {"Authorization": "API_KEY"}})
.then(response=>{
return response.json()
})
.then(res=>{
result_array.push(res.documents);
if (result_array.length===2) {
const new_result_array_2 = [...new Set((result_array))];
new_result_array_2.forEach(nra=>{
info_array.push([nra.place_name,nra.address_name])
})
//info_array is not null at this point, but the below code only return null when checked from the client
return info_array;
}
})
.catch(error=>{
console.log(error)
return 'error';
})
})
});
Thanks a lot in advance!
You should use Promise.all() instead of running each promise (fetch request) separately in a forEach loop. Also I don't see the function returning anything if result_array.length is not 2. I can see there are only 2 requests that you are making but it's good to handle all possible cases so try adding a return statement if the condition is not satisfied. Try refactoring your code to this (I've used an async function):
exports.restaurantSearch = functions.https.onCall(async (data, context) => {
// Do note the async ^^^^^
const request = data.request;
const k = encodeURIComponent(request);
const url1 = "an_url_to_call_the_external_API" + k;
const url2 = "another_url_to_call_the_external_API" + k;
const url_array = [url1, url2];
const responses = await Promise.all(url_array.map((url) => fetch(url, { headers: { "Authorization": "API_KEY" } })))
const responses_array = await Promise.all(responses.map((response) => response.json()))
console.log(responses_array)
const result_array: any[] = responses_array.map((res) => res.documents)
// Although this if statement is redundant if you will be running exactly 2 promises
if (result_array.length === 2) {
const new_result_array_2 = [...new Set((result_array))];
const info_array = new_result_array_2.map(({place_name, address_name}) => ({place_name, address_name}))
return {data: info_array}
}
return {error: "Array length incorrect"}
});
If you'll be running 2 promises only, other option would be:
// Directly adding promises in Promise.all() instead of using map
const [res1, res2] = await Promise.all([fetch("url1"), fetch("url2")])
const [data1, data2] = await Promise.all([res1.json(), res2.json()])
Also check Fetch multiple links inside of forEach loop

TypeError: tagIds.join is not a function

I am getting this error in javascript.
TypeError: tagIds.join is not a function
I am trying to join many tags id together and delete them at once, so here I used tagsId to join the tags and separator them with commas. in the console, it is working well when I tested it. but in my code, it doesn't work. here is the code.
const deleteTags = async (postId, tagIds) => {
const joinedTags = tagIds.join(",");
joinedTags = await db.sequelize.query(
`delete from posttags where postId = ${postId} and tagId in (${joinedTags});`,
{ type: db.sequelize.QueryTypes.destroy }
);
console.log("joinedTags", joinedTags);
res.json({ joinedTagsResults });
};
this is the error as well.
const joinedTags = tagIds.join(",");
^
TypeError: tagIds.join is not a function
if I am doing something wrong please let me know.
thanks
const deleteTags = async (postId, tagIds) => {
let joinedTags = tagIds;
// if tagIds is array
if (Array.isArray(tagIds)) joinedTags = tagIds.join(',');
...
};
const postId = 5;
deleteTags(postId, [315, 312]);
deleteTags(postId, "315"); // Now, this also working

How do I get my async function to return values not a promise?

I am new to async/await functionality. I have a function that the gets db info and I'd like to make the function setUpDb() wait until the promise is completed before returning.
const dbInfo = this.setUpDb();
console.log(dbInfo);
async setUpDb() {
const appLoc = path.join(homedir(), ".tsa"); // temp db info
const f: string = path.join(appLoc, "config.json");
await fs.ensureFile(f);
const temp = await fs.readJson(f);
console.log(temp);
return {
dbHost: temp.dbHost,
dbUser: temp.dbUser,
dbPass: temp.dbPass
};
}
My current output is: Promise { <pending> } then a moment later it's all the info in temp.
I just need to get the return to wait until there are values before it returns. Is that possible?
Thanks!!
You're logging before the async function completes. Just wait for the promise to resolve before logging it
let dbInfo;
this.setUpDb().then(result => {
dbInfo = result;
console.log(dbInfo);
});
You must await for the return of setUpDb function
async setup() {
const dbInfo = await this.setUpDb();
console.log(dbInfo);
}
async setUpDb() {
const appLoc = path.join(homedir(), ".tsa"); // temp db info
const f: string = path.join(appLoc, "config.json");
await fs.ensureFile(f);
const temp = await fs.readJson(f);
console.log(temp);
return {
dbHost: temp.dbHost,
dbUser: temp.dbUser,
dbPass: temp.dbPass
};
}
I think Asthmatic suggestion is the best way, but I think you can also do..
//const temp = await fs.readJson(f);
let temp;
await() => temp = fs.readJson(f);
(I may be wrong)

Firebase Functions - NodeJS: Asynchronious Array handling

I´ve checked numerous posts but could not solve the issue.
I want to return the array before going on. I tried using a function with a callback but that did not work either.
My code looks as following:
exports.GetHelmets = functions.database.ref('onTrack/{userID}').onCreate(event => {
var helmets = [];
let userID = event.params.userID;
let friendRef = admin.database().ref("friends").child(userID);
friendRef.once("value").then(snapshot => {
snapshot.forEach(function(snapshot2){
let rider = snapshot2.val();
let riderID = rider.id;
let rhRef = admin.database().ref("User").child(riderID);
rhRef.once("value", function(snapshot3){
let rider2 = snapshot3.val();
let helmetID = rider2.helmet;
if (helmetID != "STANDARD"){
if(helmets.indexOf(helmetID) < 0){
helmets.push(helmetID);
};
};
});
});
return helmets;
}).then(helmets => {
//WORK WITH ARRAY
});
I hope you can help me, thanks!
You want the last then() to get all the inner data, each of which requires its own call to once(). In such a case, you'll want to use Promise.all() to wait for all the onces.
exports.GetHelmets = functions.database.ref('onTrack/{userID}').onCreate(event => {
let userID = event.params.userID;
let friendRef = admin.database().ref("friends").child(userID);
friendRef.once("value").then(snapshot => {
var promises = []l
snapshot.forEach(function(snapshot2){
let rider = snapshot2.val();
let riderID = rider.id;
let rhRef = admin.database().ref("User").child(riderID);
promises.push(rhRef.once("value");
});
});
return Promise.all(promises);
}).then(snapshots => {
var helmets = [];
snapshots.forEach((snapshot) => {
let rider2 = snapshot.val();
let helmetID = rider2.helmet;
if (helmetID != "STANDARD"){
if(helmets.indexOf(helmetID) < 0){
helmets.push(helmetID);
};
};
});
// WORK WITH helmets
});

Resources