How to make an entire script wait for response from Firebase? - javascript

I'm getting some data from Firebase and exporting this data to another script as an object, but since script-2.js is executed before the response in the script-1.js gets returned, I'm not able to get the data in time. I'm fairly new with both Firebase and promises and I'm having a hard time with them right now.
My project is something like this:
SCRIPT 1 (fetching data and storing it into fetchedData):
let fetchedData= {}
const db = firebase.firestore()
const getTechs = () => {
return db.collection('techs').get()
}
window.addEventListener('DOMContentLoaded', async e => {
const querySnapshot = await getTechs()
querySnapshot.forEach(doc => {
const key = Object.keys(doc.data())
const value = Object.values(doc.data())
fetchedData[key] = value
})
})
export default fetchedData
SCRIPT 2 (Importing fetchedData from script-1.js
import fetchedData from './script-1.js'
console.log(fetchedData) // => here I have an empty object, since it's not populated yet.
//...do something with the data
If I was fetching the data and using it in the same script I could make this work, but I'm going to use fetchedData on multiple other scripts, so it would be nice not to have to fetch it on all of them.
How could I solve this?
P.S. I already tried with a hard coded object in script-1.js and worked fine, the problem really is what I described.

You can export a Promise which would (eventually) resolve to your fetched data:
/* script 1 */
let resolve;
const promise = new Promise(r => resolve = r);
// eventually
resolve(data);
export default promise;
/* script 2 */
import fetchedDataPromise from './script-1.js';
// whenever you need it
const fetchedData = await fetchedDataPromise;
Alternatively, you could try to delay the execution of the 2nd script. Depending on how/when that gets executed, that might be possible.

Related

js only - run a function only once

I'm fetching some data from firebase and would like to run async/await function (to fetch data) only once upon the first page load. I'm used to React and lifecycle methods / hooks doing it but this little project is just too small to use React. I just need to run this function once, fetch the data, save it to a variable and do not make any further calls to firebase api in the same session.
async function getEntries() {
const snapshot = await firebase.firestore().collection('riders').get()
// Do my thing with the data, etc.
// console.log(snapshot.docs.map(doc => doc.data()));
}
Is there any js-only way of running this function only once when the page loads?
If you call a function just once, why do you need the function at all?
const snapshot = await firebase.firestore().collection('riders').get()
// Do my thing with the data, etc.
// console.log(snapshot.docs.map(doc => doc.data()));
This top level await only works in modules, and it blocks all depending modules to load. If that is not necessary (they don't depend on the data), or if you don't want write a module, you can wrap the code in an async IIFE, and store the returned promise in a variable:
const dataPromise = (async function() {
//...
return data;
})();
While the data is loading, you might want to show some loading icon or so. That can easily be done with the following hook:
function usePromise(p) {
const [state, setState] = useState(null);
useEffect(() => { p.then(setState); }, []);
return state;
}
// Inside a component:
const data = usePromise(dataPromise);
if(data === null)
return <Loading />;
// show data
Yes. You can use Self Invoking (self executing) Functions. Syntax is like:
(function(){})();
The last parentheses are for running function. the function is anonymous.
You can Implement it this way:
(async function () {
const snapshot = await firebase.firestore().collection('riders').get()
})();
in this way you can never call this function again and it will run only once.
Tutorial: https://blog.mgechev.com/2012/08/29/self-invoking-functions-in-javascript-or-immediately-invoked-function-expression/
And The question you asked is somehow duplicate and answered here: Function in JavaScript that can be called only once
What you are looking for is memoization of the function result. There are several libraries to supporting including react.
Theres also a handmade pattern you can use by changing the function implementation after it's called once, accoring to JavaScript: The Good Parts
async function getEntries() {
const snapshot = await firebase.firestore().collection('riders').get()
// Do my thing with the data, etc.
// console.log(snapshot.docs.map(doc => doc.data()));
getEntries = async function(){
return snapshot
}
return snapshot
}
I think you can load it with the load method when the page is first loaded and then set it to cookie or local stroge. You can check this value on next page loads. You can do this quickly using jQuery.
$(window).load(function() {
var item = localStorage.getItem('test');
if(item != null){
// your code
}
else {
localStorage.setItem('test', 1);
}
});
The simplest way is to make a global variable like:
let isCalled = false;
and in the function body do:
if(isCalled) return;
//the stuff the function would do
isCalled = true;
//Assign isCalled to true before using a return statement as it will make the program discard the lines below it.

Understand Vue and Return and Async Await

I am new to Javascript and Vue and I am having a hard time wrapping my head around return, async and await work together. I come from a Python background and JS syntax is quite different
So... some background to the problem....I'm building a Vue blog and creating multiple Vuex stores using module mode. I am also creating a function to retrieve data from Prismic.
./store/blog.js
import {MyFunctions} from "../plugins/myfunctions.js";
export const actions = {
async retrievePosts() {
console.log("HELLO")
return MyFunctions.MyFunction("blog_post");
}
}
./plugins/myfunctions.js
import Prismic from "prismic-javascript";
import PrismicDom from "prismic-dom" //importing the Dom
import PrismicConfig from "../prismic.config.js";
export const MyFunctions = {
MyFunction: async function (doctype) {
console.log("Before")
const api = await Prismic.getApi(PrismicConfig.apiEndpoint)
let blog_post = {}
const results = await api.query(
Prismic.Predicates.at("document.type", doctype),
{ lang: "en-us" } //This is a Prismic query option
)
console.log("After")
result = results.results
return result;
}
};
Heres my question:
In blog.js, if I remove the word "return" in front of MyFunctions.MyFunction("blog_post") , the await in myfunctions.js do not get activated. Why?
I say this because without the return, "Before" is console logged but "After" is not and nothing is returned.
A further question for my enlightenment:
Where does return MyFunctions.MyFunction("blog_post") return to? So the data is returned to retrievePosts() but it is part of a {} which is a javascript object I believe? So does it become like a property inside {}?
Ans 1:You are actually returning a function to the caller of retrievePosts() in async mode. By removing return, function will be executed but it don't make any effect to the caller of retrievePosts() function.
Ans 2:The MyFunctions.MyFunction("blog_post") is returned to the caller of retrievePosts() function.

React Hooks, setState not working with an async .map call

So, I'm trying to change a state in my component by getting a list of users to call an api.get to get data from these users and add on an new array with the following code:
function MembersList(props) {
const [membersList, setMembersList] = useState(props.members);
const [devs, setDevs] = useState([]);
useEffect(() => {
let arr = membersList.map((dev) => {
return dev.login;
});
handleDevs(arr);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [membersList]);
function handleDevs(membersArr) {
membersArr.map(async (dev) => {
let { data } = await api.get(`/users/${dev}`);
/** If i console.log data here i actualy get the data from the user
but i think theres something wrong with that setDevs([...devs, data]) call
**/
setDevs([...devs, data]);
});
}
but the devs state always return an empty array, what can I do to get it to have the actual users data on it?
The issue you were having is because you were setting devs based on the data from the original render every time due to the closure around handleDevs function. I believe this should help take care of the issues you were having by using the callback method of using setDevs.
This also takes care of some issues with the dependency arrays and staleness in the useEffect hook. Typically using // eslint-disable-next-line react-hooks/exhaustive-deps should be your last resort.
function MembersList(props) {
// this isn't needed unless you are using it separately
const [membersList, setMembersList] = useState(props.members);
const [devs, setDevs] = useState([]);
useEffect(() => {
let arr = membersList.map((dev) => dev.login);
arr.forEach(async (dev) => {
let { data } = await api.get(`/users/${dev}`);
setDevs((devs) => [...devs, data]);
})
}, [membersList]);
}
You need to understand how React works behind scenes.
In short, it saves all the "sets" until it finishes the cycle and just after that actually update each state.
I think that why you do not see the current state updated.
For better understanding read this post: Medium article
The issue is that setDevs uses devs which is the version of devs when handleDevs is defined. Therefore setDevs will really only incorporate the data from the last time setDevs is called.
To fix this you can use the callback version of setDevs, like so:
setDevs(prevDevs => [...prevDevs, data])
Also since you are not trying to create a new array, using map is not semantically the best loop choice. Consider using a regular for loop or a forEach loop instead.
you call setDevs in the async execution loop. Here is the updated handleDevs function.
function handleDevs(membersArr) {
const arr = membersArr.map(async (dev) => {
let { data } = await api.get(`/users/${dev}`);
return data;
/** If i console.log data here i actualy get the data from the user
but i think theres something wrong with that setDevs([...devs, data]) call
**/
});
setDevs([...devs, ...arr]);
}

Hanging in a vue component with async / await call

I make my first steps with the vue framework and I can't figure out to solve the following problem.
dateClick(arg)
{
this.cal.date = arg.date;
this.cal.title = arg.resource.title;
this.cal.resource = arg.resource.id;
// const slots = (async() => {
// return await dataService.getSlots(arg);
// })().catch(console.error);
// console.log(slots);
(async() => {
const slots = await dataService.getSlots(arg);
console.log(slots);
})().catch(console.error);
}
The console.log in the async function works correct and returns the slots. Finally I would need to also set a data attribute in the current Vue Component like this.cal.slots = slots. But that doesn't work, always undefined. I also tried the commented code above to return the await - this would result in "Promise {pending}". Can't figure it out how to solve this?
Try like this:
async dateClick(arg) {
...
this.cal.slots = await dataService.getSlots(arg);
...

Firestore cloud functions summing subcollections values

sup guys, i'm working on this firebase project and i need to iterate trought a subcollection of all sales of all stores in the root collection and sum their values... the problem that i'm getting is that i'm getting the sum printed before the iteration. I'm new to TS and Firebase... this is what i got so far:
export const newBilling = functions.firestore.document('billings/{billId}').onCreate(event =>
{
const valueArray = []
const feeArray = []
const storesCollection = afs.collection('stores').where('active', '==', true).get().then(stores => {
stores.forEach(store => {
const salesCollection = afs.collection('stores').doc(store.id).collection('sales').get().then(sales => {
sales.forEach(sale => {
return valueArray.push(sale.data().value) + feeArray.push(sale.data().fee)
// other aproach
// valueArray.push(sale.data().value)
// feeArray.push(sale.data().fee)
})
})
})
}).catch(error => {console.log(error)})
let cashbackSum, feeSum : number
cashbackArray.forEach(value => {
cashbackSum += value
})
feeArray.forEach(value => {
feeSum += value
})
console.log(cashbackSum, feeSum)
return 0
})
TKS =)
You're not using promises correctly. You've got a lot of get() method call, each of which are asynchronous and return a promise, but you're never using them to case the entire function to wait for all the work to complete. Calling then() doesn't actually make your code wait - it just runs the next bit of code and returns another promise. Your final console.log is executing first because none of the work you kicked off ahead of it is complete yet.
Your code actually needs to be substantially different in order to work correctly, and you need to return a promise from the entire function that resolves only after all the work is complete.
You can learn better how to use promises in Cloud Functions by going through the video tutorials.

Categories