Suppose I have the following Firestore data structure:
collection tickets
{
name: "Testticket",
open: true,
users: [
"users/abcdefg1234" // Firestore reference
]
}
collection users
{
name: "John Doe",
color: "green"
}
My goal is to get all the tickets where open is true, including the user object istead of the reference.
Currently I am doing this:
// Firebase Functions node.js code
async function getUserData(item) {
const queryResult = await item.get();
return await queryResult.data();
}
exports.getOpenTickets = async (request, response) => {
const query = await db.collection("tickets").where("open", "==", true).get();
let tickets = [];
query.forEach(async (doc) => {
let data = doc.data();
console.log("data", data);
data.userObjects = await Promise.all(data.users.map(getUserData));
console.log("data.userObjects", data.userObjects);
tickets.push(data);
});
return response.json(tickets);
};
Problem: The user data is fetched but the main function is not waiting for it (checked with console log statements).
This is my node.js console log:
i functions: Beginning execution of "api"
! Google API requested!
- URL: "https://oauth2.googleapis.com/token"
- Be careful, this may be a production service.
> data {
> open: true,
> name: 'Testticket',
> users: [
> DocumentReference {
> _firestore: [Firestore],
> _path: [ResourcePath],
> _converter: [Object]
> }
> ]
> }
i functions: Finished "api" in 1069.554ms
> data.userObjects [ { color: 'green', name: 'John Doe' } ]
How to fix this?
If you want to run asynchronous operations within a loop, then you should not use forEach. Try refactoring the code using for of loop as shown below:
exports.getOpenTickets = async (request, response) => {
const snap = await db.collection("tickets").where("open", "==", true).get();
let tickets = [];
for (const doc of snap.docs ) {
let data = doc.data();
console.log("data", data);
data.userObjects = await Promise.all(data.users.map(getUserData));
console.log("data.userObjects", data.userObjects);
tickets.push(data);
}
return response.json(tickets);
};
Also checkout Using async/await with a forEach loop.
Related
I need to query some data from Redshift in my aws lambda code and I do it several times with different parameters, pretty soon I get the error:
ActiveStatementsExceededException: Active statements exceeded the allowed quota (200).
How can I send thousands of queries to Redshift without hitting a limit?
Here is my JS code:
...
let results = await redshiftQuery(connectionInfo, "SELECT * from db.games where game_id=:game_id", {game_id: 12345})
...
async function redshiftQuery(conInfo, query, params = {}) {
let rsParams = []
for(let key in params) {
rsParams.push( { name: key, value: params[key].toString() })
}
const executeStatementInput = {
ClusterIdentifier: conInfo.clusterId,
Database: conInfo.database,
SecretArn: conInfo.secret,
WithEvent: false,
Sql: query
}
if(rsParams.length) {
executeStatementInput.Parameters = rsParams
}
let results = []
try {
let exResponse = await redshiftDataApiClient.executeStatement(executeStatementInput).promise()
if(!exResponse.Id) return results
let describeStatementInfo = null
while(true) {
let { Status: queryStatus, ...rest } = await redshiftDataApiClient.describeStatement({ Id: exResponse.Id }).promise()
describeStatementInfo = rest
if(["FAILED", "FINISHED"].includes(queryStatus)) {
break
}
await sleepMillis(200)
}
if (describeStatementInfo && describeStatementInfo.HasResultSet) {
let result = await redshiftDataApiClient.getStatementResult({ Id: exResponse.Id }).promise()
convertResult(result, results)
while(result.NextToken) {
result = await redshiftDataApiClient.getStatementResult({ Id: exResponse.Id, NextToken: result.NextToken }).promise()
convertResult(result, results)
}
}
} catch (e) {
console.error("Redshift Error", e)
}
return results
}
Like
[
{
"enear": "",
"inten": 1,
"sctor": "Eny",
"topic": "",
"insight": ""
},
{
"enear": "",
"inten": 1,
"sctor": "Eny",
"topic": "",
"insight": ""
}
]
If possible how to write the nodejs code
This is my code
router.post("/post" , async (req,res) => {
const data = new Model(req.map(r => ({
enear: r.body.enear,
inten:r.body.inten,
sctor: r.body.sctor,
topic: r.body.topic,
insight: r.body.insight,
})))
try{
const dataToSave = await data.save()
res.status(200).json(dataToSave)
}catch(error){
res.status(400).json({message:error.message})
}
})
Does map works here?
I have tried using map . Is there any possible way please suggest
You can send an array in the body part and access an array using
req.body and use req.body.map here if it satisifies the condition
Array.isArray(req.body)
router.post("/post" , async (req,res) => {
const { body } = req;
if (Array.isArray(body)) {
const data = new Model(body.map(r => ({
enear: r.body.enear,
inten:r.body.inten,
sctor: r.body.sctor,
topic: r.body.topic,
insight: r.body.insight,
})))
try{
const dataToSave = await data.save()
res.status(200).json(dataToSave)
}catch(error){
res.status(400).json({message:error.message})
}
}
You can specify the array in Body -> Raw (select JSON format):
Then, you should be able to access your data with:
router.post('/post', async (req, res) => {
const { array } = req.body;
try {
const savedData = [];
for (const obj of array) {
const data = await Model.create({
enear: obj.enear,
inten: obj.inten,
sctor: obj.sctor,
topic: obj.topic,
insight: obj.insight,
});
savedData.push(data);
}
res.status(200).json(savedData);
} catch (error) {
res.status(400).json({ message: error.message });
}
});
Basically what I want is that if a document doesn't exist then create a new one (it works fine now) but if a document does exist, push a new object to the existing array.
I was able to get data from documents and console.log them, but don't know how to push new ones to the existing document.
My FB structure looks like this:
favorites
someUserID
Videos [
0: {
name: SomeName
url: SomeUrl
},
/* I would like to push new objects like this: */
1: {
name: data.name
url: data.url
},
]
This is my current code:
const { user } = UserAuth();
const UserID = user.uid;
const favoritesRef = doc(db, "favorites", UserID);
const test = async (data) => {
try {
await runTransaction(db, async (transaction) => {
const sfDoc = await transaction.get(favoritesRef);
if (!sfDoc.exists()) {
setDoc(favoritesRef, {
Videos: [{name: data.name}]
});
}
/* I got my document content here */
const newFavorites = await getDoc(favoritesRef);
console.log("Document data:", newFavorites.data());
/* And would like to push new Data here */
transaction.update(favoritesRef, { name: data.name});
});
console.log("Transaction successfully committed!");
} catch (e) {
console.log("Transaction failed: ", e);
}
}
To update the array Firestore now has a function that allows you to update an array without writing your code again:
Update elements in an array
If your document contains an array field, you can use arrayUnion()
and arrayRemove() to add and remove elements. arrayUnion() adds
elements to an array but only elements not already present.
arrayRemove() removes all instances of each given element.
import { doc, updateDoc, arrayUnion, arrayRemove } from "firebase/firestore";
const washingtonRef = doc(db, "cities", "DC");
// Atomically add a new region to the "regions" array field.
await updateDoc(washingtonRef, {
regions: arrayUnion("greater_virginia")
});
// Atomically remove a region from the "regions" array field.
await updateDoc(washingtonRef, {
regions: arrayRemove("east_coast")
});
Not sure if this helps but what i usually do is:
I'm adding every user that signs in to an array.
const snapshot = await getDoc(doc(db, "allUsers", "list"));
const currentUsers = snapshot.data().users;
await setDoc(doc(db, "allUsers", "list"), {
users: [...currentUsers, { name, uid: userId, avatar: photo }],
});
I first get the items that exist in the list, and them i create a new one that has every previous item and the ones i'm adding. The currentUsers is the current list in that caso. Maybe you should try thist instead of Videos: [{name: data.name}]
setDoc(favoritesRef, {
Videos: [...currentVideos, {name: data.name}]
})
I just figured it out like this:
const { user } = UserAuth();
const UserID = user.uid
const favoritesRef = doc(db, "favorites", UserID)
const test = async (data) => {
try {
await runTransaction(db, async (transaction) => {
const sfDoc = await transaction.get(favoritesRef);
if (!sfDoc.exists()) {
await setDoc(favoritesRef, {
favs: [
{
name: data.name,
ytb: data.ytb,
url: data.url
}]})
}
const doesExists = sfDoc.data().favs.some((fav) => fav.name === data.name)
console.log(doesExists)
if (doesExists === true)
{
console.log("AlreadyExist")
}
else {
const currentData = sfDoc.data().favs
transaction.update(favoritesRef, {
favs: [...currentData,
{
name: data.name,
ytb: data.ytb,
url: data.url
}]}
)}
});
console.log("Transaction successfully committed!");
} catch (e) {
console.log("Transaction failed: ", e);
}
}
I have this function, I created it but then I'm getting confused and don't know how to return the data.
I have tried Promise.all() before but it's seems I do not quite understand it so I have removed it from my code, I don't know if it's a correct way to do it or not.
I'm following this AniList Node Document
Here's how the code work. I'm using POSTMAN to query the Title, for example, One Piece, it'll then search the query title and get the ID of that Title in AniList. Then, it's using that ID to find all the info (it's in the detailInfo)
Here's my Model:
static async getAnilist(title) {
const Anilist = new anilist()
const animeInfo = Anilist.searchEntry
.anime(title, null, 1, 1)
.then((titleToID) => {
const animeID = titleToID.media[0].id
const detailInfo = Anilist.media.anime(animeID).then((data) => {
return {
AnimeID: animeID,
Schedule: data.airingSchedule[0],
Score: data.averageScore,
BannerImg: data.bannerImage,
Character: data.characters,
Country: data.countryOfOrigin,
CoverImg: data.coverImage,
Duration: data.duration,
EndDate: data.endDate,
EpisodeTotal: data.episodes,
Genre: data.genres,
Season: data.season,
SeasonYear: data.seasonYear,
Status: data.status,
Studio: data.studios,
UpdateAt: data.updatedAt,
}
})
return detailInfo
})
return animeInfo
}
Here's my Controller:
static async getAnilist(req, res, next) {
const { title } = req.query
try {
const { data } = await Model.getAnilist(title)
res.json({
success: true,
data: data,
})
} catch (err) {
next(err)
}
}
What I'm hoping for:
"success" : true,
"data" : {
AnimeID,
Schedule,
Score,
BannerImg,
...
UpdateAt
}
What I'm getting right now
"success" : true
but without any data due to I can't return it.
The request is succeeded, but I don't know how to actually return it from nested promise.
Here's what I get from using console.log({AnimeID, Schedule...}) instead of return
In async...await, async expects an await to follow. In your model you are declaring the function as async but inside you have promise. Easiest solution is to use await instead of promise.
static async getAnilist(title) {
const Anilist = new anilist()
const titleToId = await Anilist.searchEntry.anime(title, null, 1, 1);
const animeID = titleToID.media[0].id;
const data = await Anilist.media.anime(animeID);
const detailInfo = {
AnimeID: animeID,
Schedule: data.airingSchedule[0],
Score: data.averageScore,
BannerImg: data.bannerImage,
Character: data.characters,
Country: data.countryOfOrigin,
CoverImg: data.coverImage,
Duration: data.duration,
EndData: data.endDate,
EpisodeTotal: data.episodes,
Genre: data.genres,
Season: data.season,
SeasonYear: data.seasonYear,
Status: data.status,
Studio: data.studios,
UpdateAt: data.updatedAt,
};
const animeInfo = detailInfo;
return animeInfo;
}
NB: You can optimize the above to be more consise. I translated it as-is.
I'm trying to create a function with firebase, where upon request the function carries out some scraping activites and then logs the result to a collection each time. My function works and returns the Array of items that I need, but I am having trouble then adding this array to the firestore database.
I am not sure if I need to subscribe to the response or if it is returning something else.
Cloud Function:
exports.scraper = functions.https.onRequest( async (request, response) => {
cors(request, response, async () => {
const body = (request.body);
const data = await scrapeteamtags(body.text);
response.send(data)
});
return admin.firestore().collection('games').add({
teams: data
})
});
Added the function used in the await for context:
const scrapeteamtags = (text) => {
const urls = Array.from( getUrls(text) );
const requests = urls.map(async url => {
const res = await fetch(url);
const html = await res.text();
const $ = cheerio.load(html);
const getTeamlist = JSON.parse($('body').text())
var gamelist = {
games: []
}
getTeamlist.SSResponse.children.map(function(item) {
// go into the returned json
var event = new Object;
var leagues = ["Premier League", "Spanish La Liga", "Italian Serie A", 'French Ligue 1', 'German Bundesliga']
// finds all child items that contain the event tag
if(Object.keys(item).includes('event')) {
// check that the league is on the list which are of interest
if(leagues.includes(item.event.typeName)) {
event.id = item.event.id;
event.name = item.event.name;
// add the event name and id to the object then go into next level to get market data
item.event.children.map(function(item1) {
if(Object.keys(item1).includes('market')) {
event.marketid = item1.market.id
// add the market data id to the object
var eventoutcome = []
item1.market.children.map(function(item2) {
if(Object.keys(item2).includes('outcome')) {
eventoutcome.push({"id" : item2.outcome.id,
"id": item2.outcome.id,
"name": item2.outcome.name,
"price": item2.outcome.children[0].price.priceDec})
//adds the id, name and price to an array, then add it to the object
event.outcome = eventoutcome
}
})
}
})
gamelist.games.push(event)
}
// push each event as a new object to the array of games
}
})
//console.log(gamelist.games)
return {
gamelist
}
});
return Promise.all(requests);
}
HTTP functions don't let you return a promise with the data to send. (That's how callable functions work, but that doesn't apply here.) You will have to wait for the database write to finish, then send the response to terminate the function.
The function should be structured more like this:
exports.scraper = functions.https.onRequest( async (request, response) => {
cors(request, response, async () => {
const body = (request.body);
const data = await scrapeteamtags(body.text);
await admin.firestore().collection('games').add({
teams: data
})
response.send(data)
});
});