Need the second firebase query to start after the first ends javascript - javascript

I'm a beginner in javascript and recently I've been working on a project and in this project I'm trying to save initial score in an empty firebase database which works perfectly fine. However just as the score is saved I want to retrieve it and do some calculations on it. I've tried setTimeout but it didn't work. Btw if there are scores in the firebase already it's working fine.
This is my code and thanks in advance:
function resultOne() {
var firstScore = trim(newGroup[0]);
scores(firstScore);
setTimeout(function() {return true;}, 30000);
var firstguyScore = getScore(firstScore)
console.log(firstGuyScore);
}
This is a function to set the initial score of 1500 and set name....
function scores(firstGuy) {
// Firebase query to increment the chosen girl and her seen status by 1 and to initialize each score by 1500
let ref = firebase.database().ref("scores");
let query = ref.orderByChild("name").equalTo(firstGuy);
query.once("value").then((snapshot) => {
if (snapshot.exists()) {
snapshot.forEach((userSnapshot) => {
let userRef = userSnapshot.ref;
userRef.child("chosen").transaction((currentValue) => {
return currentValue + 1;
});
userRef.child("seen").transaction((currentValue) => {
return currentValue + 1;
});
});
}
else {
ref.push({
name: firstGuy,
chosen: 1,
seen: 1,
score: 1500
});
}
});
and this is a function to retreive the data
async function getScore(firstGuy) {
let ref = firebase.database().ref("scores");
let query = ref.orderByChild("name").equalTo(firstGuy);
const snapshot = await query.once("value")
if (snapshot.exists()) {
snapshot.forEach((userSnapshot) => {
var userData = userSnapshot.val();
score = userData.score;
console.log(score);
});
}
}

setTimeout() calls the function (callback) you provide after a certain time. It does not block and wait. Your call to getScores() is executed immediately, as you can see in the console.
You can change the code as follows:
function resultOne() {
const firstScore = trim(newGroup[0]);
scores(firstScore);
setTimeout(() => {
const firstguyScore = getScore(firstScore);
console.log(firstGuyScore);
}, 30000);
}
Using setTimeout() this way is okay for testing and debugging. You should not use it this way in your production-ready code.
Why not await on scores() as well?
async function scores(firstGuy) {
...
const snapshot = await query.once("value");
...
}
async function resultOne() {
const firstScore = trim(newGroup[0]);
await scores(firstScore);
const firstguyScore = await getScore(firstScore);
console.log(firstGuyScore);
}

Related

Google Apps Script Working on backend but not on sheets

I am trying to create a script that pulls from the coin market cap API and displays the current price. The script is working fine on the back end when I assign the variable a value. However, when I try to run the function on sheets the returned value is null.
function marketview(ticker) {
var url = "https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest?CMC_PRO_API_KEY=XXX&symbol=" + ticker;
var data = UrlFetchApp.fetch(url);
const jsondata = JSON.parse(data);
Logger.log(jsondata.data[ticker].quote['USD'].price)
}
My execution logs show that the scripts are running, but when when I use the function and try and quote ETH for example, the script is running for BTC.
When I do this on the backend and assign ETH the script works fine and returns the right quote. Any ideas on what I'm missing?
I did the same with coingecko API and add an issue having all my requests being rejected with quota exceeded error.
I understood that Google sheets servers IPs address were already spamming coingecko server. (I was obviously not the only one to try this).
This is why I used an external service like apify.com to pull the data and re-expose data over their API.
This is my AppScripts coingecko.gs:
/**
* get latest coingecko market prices dataset
*/
async function GET_COINGECKO_PRICES(key, actor) {
const coinGeckoUrl = `https://api.apify.com/v2/acts/${actor}/runs/last/dataset/items?token=${key}&status=SUCCEEDED`
return ImportJSON(coinGeckoUrl);
}
You need ImportJSON function, available here: https://github.com/bradjasper/ImportJSON/blob/master/ImportJSON.gs
Then in a cell I write: =GET_COINGECKO_PRICES(APIFY_API_KEY,APIFY_COINGECKO_MARKET_PRICES), you will have to create two field named APIFY_API_KEY and APIFY_COINGECKO_MARKET_PRICES in order for this to work.
Then register on apify.com, then you'll have to create an actor by forking apify-webscraper actor.
I set the StartURLs with https://api.coingecko.com/api/v3/coins/list, this will give me the total number of existing crypto (approx 11000 as of today), and number of page so I can run the request concurrently (rate limit is 10 concurrent requests on coingecko), then I just replace /list with /market and set the proper limit to get all the pages I need.
I use the following for the tasks page function:
async function pageFunction(context) {
let marketPrices = [];
const ENABLE_CONCURRENCY_BATCH = true;
const PRICE_CHANGE_PERCENTAGE = ['1h', '24h', '7d'];
const MAX_PAGE_TO_SCRAP = 10;
const MAX_PER_PAGE = 250;
const MAX_CONCURRENCY_BATCH_LIMIT = 10;
await context.WaitFor(5000);
const cryptoList = readJson();
const totalPage = Math.ceil(cryptoList.length / MAX_PER_PAGE);
context.log.info(`[Coingecko total cryptos count: ${cryptoList.length} (${totalPage} pages)]`)
function readJson() {
try {
const preEl = document.querySelector('body > pre');
return JSON.parse(preEl.innerText);
} catch (error) {
throw Error(`Failed to read JSON: ${error.message}`)
}
}
async function loadPage($page) {
try {
const params = {
vs_currency: 'usd',
page: $page,
per_page: MAX_PER_PAGE,
price_change_percentage: PRICE_CHANGE_PERCENTAGE.join(','),
sparkline: true,
}
let pageUrl = `${context.request.url.replace(/\/list$/, '/markets')}?`;
pageUrl += [
`vs_currency=${params.vs_currency}`,
`page=${params.page}`,
`per_page=${params.per_page}`,
`price_change_percentage=${params.price_change_percentage}`,
].join('&');
context.log.info(`GET page ${params.page} URL: ${pageUrl}`);
const page = await fetch(pageUrl).then((response) => response.json());
context.log.info(`Done GET page ${params.page} size ${page.length}`);
marketPrices = [...marketPrices, ...page];
return page
} catch (error) {
throw Error(`Fail to load page ${$page}: ${error.message}`)
}
}
try {
if (ENABLE_CONCURRENCY_BATCH) {
const fetchers = Array.from({ length: totalPage }).map((_, i) => {
const pageIndex = i + 1;
if (pageIndex > MAX_PAGE_TO_SCRAP) {
return null;
}
return () => loadPage(pageIndex);
}).filter(Boolean);
while (fetchers.length) {
await Promise.all(
fetchers.splice(0, MAX_CONCURRENCY_BATCH_LIMIT).map((f) => f())
);
}
} else {
let pageIndex = 1
let page = await loadPage(pageIndex)
while (page.length !== 0 && page <= MAX_PAGE_TO_SCRAP) {
pageIndex += 1
page = await loadPage(pageIndex)
}
}
} catch (error) {
context.log.info(`Fetchers failed: ${error.message}`);
}
context.log.info(`End: Updated ${marketPrices.length} prices for ${cryptoList.length} cryptos`);
const data = marketPrices.sort((a, b) => a.id.toLowerCase() > b.id.toLowerCase() ? 1 : -1);
context.log.info(JSON.stringify(data.find((item) => item.id.toLowerCase() === 'bitcoin')));
function sanitizer(item) {
item.symbol = item.symbol.toUpperCase()
return item;
}
return data.map(sanitizer)
}
I presume you are hiting the same issue I had with coinmarketcap, and that you could do the same with it.
You're not return ing anything to the sheet, but just logging it. Return it:
return jsondata.data[ticker].quote['USD'].price

Is there a way count how many times an async function is called?

I have function that uploads files from an array of data links
What I would like to do is if data links array contains 3 files
const testLinks = 3; and async uploadImageData is fired three times I would like to console.log after uploadImageData is fired three times.
I am thinking of doing a count but all my testing has the count starting over everytime uploadImageData is fired.
.ts
async uploadImageData(formData: FormData) {
const testLinks = 3;
const uploadlink = answerAtachmentUrl;
const headers = headerLink;
const loading = await this.loadingController.create({
message: 'Uploading Photos and Files...',
});
await loading.present();
this.httpClient.post<any>( uploadlink + this.userToken, formData,
{ 'headers':headers }
).pipe(
finalize(() => { loading.dismiss();})
)
.subscribe(res => {
if (res['success']) {
setTimeout(() => { this.DeleteAllFiles(); }, 5000);
this.presentToastPhoto('Photo sync success.');
} else {
this.presentToastPhoto('Photo upload failed.');
let respFail = JSON.stringify(res);
console.log("respFail", respFail);
}
});
// console.log fires once after count and const testLinks both equal 3
}
"I am thinking of doing a count but all my testing has the count starting over evertime uploadImageData is fired."
you didn't post what you tried but you probably did not make the counter a global variable. Your code is clearly part of a larger project so I just made this small test to show that it works with async functions. If it doesn't work, let me know.
var count=0;
async function uploadImageData() {
const testLinks = 3;
count++;
console.log(count);
if (count === 3){
console.log('count = 3');
}
}
uploadImageData();
uploadImageData();
uploadImageData();
uploadImageData();

Firebase Functions not firing for Cloud Firestore

I simply cannot see where I'm going wrong here. My Cloud Firestore is on "europe-west3", the functions are deployed to "europe-west1" (the docs tell me that this is the closest location to west3).
Structure is thus: I've got a bunch of "tickets" each of which can have a subcollection named "comments". The console thus looks like this:
The upload was successful:
The function code was taken from the official code samples
Github repo for Function samples
This is what my code looks like:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.countComments = functions.region('europe-west1').database.ref('/tickets/{ticketId}/comments/{commentsid}')
.onWrite(
async (change) => {
const ticketsRef = change.after.ref.parent;
const countRef = ticketsRef.parent.child('comments_count');
let increment;
if(change.after.exists() && !change.before.exists()) {
increment = 1;
} else if(!change.after.exists() && change.before.exists()) {
increment = -1;
} else {
return null;
}
await countRef.transaction((current) => {
return (current || 0) + increment;
});
console.log('Counter updated');
return null;
});
exports.recountComments = functions.region('europe-west1').database.ref('/tickets/{ticketId}/comments_count')
.onDelete(
async (snap) => {
const counterRef = snap.ref;
const collectionRef = counterRef.parent.child('comments');
const commentsData = await collectionRef.once('value');
return await counterRef.set(commentsData.numChildren());
}
)
Now, the problem is that these functions simply do not fire. I'm not seeing anything in the logs, regardless of whether I'm pushing changes through my clients (a Flutter app) or if I'm changing things directly in the Firebase console.
In my desperation I've also tried to simply listen to "/tickets" as any changes below that path should also trigger - but there's nothing.
So. What is the obvious thing I overlooked? And, yes, I had a look at the other questions/answers but nothing jumped at me...
edit:
This would be the corrected version, probably not optimal.
exports.countComments = functions.region('europe-west1').firestore.document('/tickets/{ticketId}/comments/{commentsId}')
.onWrite(
async (change, context) => {
const ticketId = context.params.ticketId;
const ticketRef = admin.firestore().collection('tickets').doc(ticketId);
let increment;
if(change.after.exists && !change.before.exists) {
increment = 1;
} else if(!change.after.exists && change.before.exists) {
increment = -1;
} else {
return null;
}
return transaction = admin.firestore().runTransaction(t => {
return t.get(ticketRef)
.then(doc => {
let count = (doc.data().comments_count || 0) + increment;
t.update(ticketRef, {comments_count: count});
});
}).then(res => {
console.log('Counter updated');
}).catch(err => {
console.log('Transaction error:', err);
});
});
Your database is Cloud Firestore, but you've written a Realtime Database trigger. They are two completely different databases. Follow the documentation for writing Cloud Firestore triggers instead.
Your function will start like this:
functions.region('europe-west1').firestore.document('...')
Note "firestore" instead of "database".

Code snippet executes after function is finished in Firebase Cloud Functions

I have a bit of a problem using firebase cloud functions. The code bellow is a function that writes to the Firestore DB an object that contains 2 arrays. After deploying the function, the idf_words array is populated, and the idf_weight is empty.
I tried placing a few log messages in the for loop and found the the query.get() executes after the function ends. Is there a way to make firestore wait until the query.get() finishes?
export const updateCakeAndPastriesIDF = functions.firestore.document("TF/tf/Cake_and_Pastries/{itemCategory}")
.onUpdate((change, context) => {
const itemBefore = change.before.data();
const itemAfter = change.after.data();
if (itemAfter['tf_tf_score'] === itemBefore['tf_tf_score']){
console.log('This TF score of the words in this item has not changed');
return null;
} else {
console.log('This TF score of the words in this item has changed');
const tfWords:string[] = itemAfter['tf_unique_words'];
const tfItemUid:string = itemAfter['tf_item_uid'];
const idfWords:string[] = [];
const idfWeight: number[] = [];
const db = admin.firestore().collection('TF').doc('tf').collection('Cake_and_Pastries');
tfWords.forEach(function (tfword) {
idfWords.push(tfword);
const query = db.where("tf_unique_words", "array-contains", tfword);
query.get().then(function (itemDoc) {
if (!itemDoc.empty){
const numberOfDocs = itemDoc.size;
console.log("For item: "+tfItemUid+", there are "+numberOfDocs+"Documents");
admin.firestore().collection('Number_of_Items')
.doc('Cake_and_Pastries')
.get()
.then(function (numberDoc){
const numberOfCakesAndPastries = numberDoc.data()['number_of_items_in_category'];
const idfOfWord = (Math.log(numberOfDocs/numberOfCakesAndPastries)+1);
idfWeight.push(idfOfWord);
console.log("Word IDF: "+idfOfWord);
console.log(idfWeight);
})
}else {
console.log("No such document!");
}
})
});
console.log("IDF weight array outside of loop: "+idfWeight);
admin.firestore()
.collection('IDF')
.doc('idf')
.collection('Cake_and_Pastries')
.doc(tfItemUid).set({
idf_item_uid: tfItemUid,
idf_words: idfWords,
idf_weight: idfWeight
});
}
});

Batch get DocumentReferences?

I'm trying to improve a firestore get function, I have something like:
return admin.firestore().collection("submissions").get().then(
async (x) => {
var toRet: any = [];
for (var i = 0; i < 10; i++) {
try {
var hasMedia = x.docs[i].data()['mediaRef'];
if (hasMedia != null) {
var docData = (await x.docs[i].data()) as MediaSubmission;
let submission: MediaSubmission = new MediaSubmission();
submission.author = x.docs[i].data()['author'];
submission.description = x.docs[i].data()['description'];
var mediaRef = await admin.firestore().doc(docData.mediaRef).get();
submission.media = mediaRef.data() as MediaData;
toRet.push(submission);
}
}
catch (e) {
console.log("ERROR GETTIGN MEDIA: " + e);
}
}
return res.status(200).send(toRet);
});
The first get is fine but the performance is worst on the line:
var mediaRef = await admin.firestore().doc(docData.mediaRef).get();
I think this is because the call is not batched.
Would it be possible to do a batch get on an array of mediaRefs to improve performance?
Essentially I have a collection of documents which have foreign references stored by a string pointing to the path in a separate collection and getting those references has been proven to be slow.
What about this? I did some refactoring to use more await/async code, hopefully my comments are helpful.
The main idea is to use Promise.all and await all the mediaRefs retrieval
async function test(req, res) {
// get all docs
const { docs } = await admin
.firestore()
.collection('submissions')
.get();
// get data property only of docs with mediaRef
const datas = await Promise.all(
docs.map(doc => doc.data()).filter(data => data.mediaRef),
);
// get all media in one batch - this is the important change
const mediaRefs = await Promise.all(
datas.map(({ mediaRef }) =>
admin
.firestore()
.doc(mediaRef)
.get(),
),
);
// create return object
const toRet = datas.map((data: MediaSubmission, i) => {
const submission = new MediaSubmission();
submission.author = data.author;
submission.description = data.description;
submission.media = mediaRefs[i].data() as MediaData;
return submission;
});
return res.status(200).send(toRet);
}

Categories