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.
Related
Ok the goal is to be equal a global scope variable to an inner information I need and then export it. But I am not able to do it (I keep getting undefined even though when I console out data, I get the info I want). I have looked at several documentations and other peoples questions NodeJS Async/Await Exporting a Variable , but it still doesn't answer my question. I know I have to use asynchronous JavaScript (async, await) but because I fairly new to JS. NOTE: GETAPRODUCTAPI is a SpringBoot API and updateClick() is called inside another method.
The global variable called dataToExport I want to export and it equals data (then(data)).
export var dataToExport;
const updateClick = () => {
const editBtns = getQSelectorAll(".edit");
editBtns.forEach((btn) => {
btn.addEventListener("click", (e) => {
const currentClicked = e.currentTarget.dataset.editid;
const api = GETAPRODUCTAPI + currentClicked;
fetch(api).then((response) => {
return response.json();
}).then((data) => {
console.log(data); // this works fine
// TODO
dataToExport = data
});
});
});
};
console.log(dataToExport); // undefined output
This works but you have to ensure that reading of dataToExport happens after the asynchronous code that actually sets the variable.
That is, through whatever means necessary, you would want to wait to console.log(dataToExport) until after all of the following have occurred:
updateClick is called and the event listener has been bound to the buttons
The button(s) are actually clicked (programmatically or by user interaction)
The fetch(es) are sent out
The response to the fetch(es) are received
The response json is parsed and assigned to dataToExport.
As is you're trying to read dataToExport before any of those steps have occurred and therefore the variable has not been set yet. You can periodically poll to see if the variable has been assigned (e.g. setTimeout or setInterval) or otherwise have an event that triggers a check of this variable.
Based on #arcyqwerty explanation, I was able to figure out a logic. I created two global scopes a variable status and a function changedStatus() . Equalled status to null and then changedStatus() whose job is to check when status changes from null to something else (event that triggers a check of this variable). NOTE: I renamed dataToExport to status
let status = null;
let changedStatus = (value) => {
status = value;
if (status != null) {
console.log(status);
}
}
const updateClick = () => {
const editBtns = getQSelectorAll(".edit");
editBtns.forEach((btn) => {
btn.addEventListener("click", (e) => {
const currentClicked = e.currentTarget.dataset.editid;
const api = GETAPRODUCTAPI + currentClicked;
fetch(api).then((response) => {
return response.json();
}).then((data) => {
// TODO
changedStatus(data);
});
});
});
};
And as far as exporting dataToExport just like #Bergi said it doesn't make sense to export data before user clicks so instead I used localStorage. This example helped for better understanding.
i don't really understand async. i have a function like this:
function getTeam() {
let sir = setInterval(() => {
const teamsGrid = $('[class*="teamsgrid"]').find("p");
const firstTeam = $(teamsGrid[0]).text();
if (firstTeam != '') {
clearInterval(sir)
return firstTeam.trim()
}
}, 100)
}
im not js master. i just want to get that element when it loads in, this code is running in a userscript and // #run-at document-idle doesnt help either. i knew i would have to get into async js promises callbacks and whatever someday but i really dont understand how it works after pages of docs and other stackoverflow.
when i console.log this function it will print undefined once then if i have a console.log inside the if it will print the actual team name.
how do i wait for that result
Answer regarding the javascript language part if the question
You could modify your code to the following (but don't - see further below - I'm just providing this as your StackOverflow tags included async/await):
async function getTeam() {
return new Promise(resolve => {
const sir = setInterval(() => {
const teamsGrid = $('[class*="teamsgrid"]').find("p");
const firstTeam = $(teamsGrid[0]).text();
if (firstTeam != '') {
clearInterval(sir);
resolve(firstTeam.trim());
}
}, 100);
});
}
// ... and then anywhere else in your code:
doSomethingSynchronous();
const team = await getTeam();
soSomethingSynchronousWithTeam(team);
Note that this will only work with modern browsers supporting >= ECMAScript 2017:
https://caniuse.com/async-functions (but luckily that's most by now!)
Answer regarding the implicit "howto wait for an element part"
... you really shouldn't actively wait for an element because this is unnecessarily heavy on the CPU. Usually you'll have some kind of event that informs you as soon as the element you're waiting for has been created. Just listen for that and then run your code.
What to do, if there's currently no such event:
If you're in control of the code creating the element, then trigger one yourself (see https://api.jquery.com/trigger/ for example).
If the element is created by a third party lib or by something else you cannot easily modify, you could use a MutationObserver (see this StackBlitz answer to a related question) and run your getTeam code only whenever something has changed instead of every 100ms (smaller impact on performance!)
You can make it async if you want, but the main part us going to be using events instead. There is a special object called mutation observer. It will call a function you give it any time there's a change in the element you're observing.
Check the mutation observer docs to understand the code below: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
Without knowing much about your HTML, I can say as much as this should work:
function getTeam() {
const teamsGrid = $('[class*="teamsgrid"]').find("p");
const firstTeam = $(teamsGrid[0]).text();
if (firstTeam != '') {
return firstTeam.trim()
}
}
function getTeamWhenAvailable() {
// returning a promise allows you to do "await" and get result when it is available
return new Promise((resolve, reject) => {
// furst try if element is available right now
const teamText = getTeam();
if(teamText) {
// resolve() "returns" the value to whoever is doing "await"
resolve(teamText);
// resolve does not terminate this function, we need to do that using return
return;
}
// Mutation observer gives you list of stuff that changed, but we don't care, we just do our own thing
const observer = new MutationObserver(()=>{
const teamText = getTeam();
if(teamText) {
// stop observing
observer.disconnect();
// resolve the value
resolve(teamText);
}
});
observer.observe(document.body, { childList: true, subtree: true };
})
}
// usage, the extra brackets around the lambda cause it to invoke immediatelly
(async () => {
console.log("Waitinf for team...");
const teamName = await getTeamWhenAvailable();
console.log("Result team name: ", teamName)
})();
Now you might wanna narrow the scope of the mutation observer, in the example above it watches the entire document. Try to instead observe the deepest element that you can rely on not being removed.
If you need to receive team name multiple times, I think you should just go with the obsever alone without the async stuff.
function getTeam() {
let sir = new Promise((res, rej) => {
const teamsGrid = $('[class*="teamsgrid"]').find("p");
const firstTeam = $(teamsGrid[0]).text();
if (firstTeam != '') {
clearInterval(sir);
res(firstTeam.trim());
}
});
return sir();
}
From what I understood, you are looking for firstTeam. Also, we assume that there is always firstTeam, so there isnt a case when there would be no team name.
I am not sure where you are making a req that will take time to process honestly from this code. So far it looks that sync function should do just fine. Are you reaching out to any API?
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.
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]);
}
I'm creating a Node.js module to interact with my API, and I use the superagent module to do the requests. How it works:
module.exports = data => {
return getUploadUrl()
.then(uploadFiles)
.then(initializeSwam)
function getUploadUrl() {
const request = superagent.get(....)
return request
}
function uploadFiles(responseFromGetUploadUrl) {
const request = superagent.post(responseFromGetUploadUrl.body.url)
// attach files that are in data.files
return request
}
function initializeSwam(responseFromUploadFilesRequest) {
// Same thing here. I need access data and repsonseFromUploadFilesRequest.body
}
}
I feel like I'm doing something wrong like that, but I can't think in a better way to achieve the same result.
Two simple ways:
write your function to take all parameters it needs
const doStuff = data =>
getUploadUrl()
.then(uploadFiles)
.then(initializeSwam)
might become
const doStuff = data =>
getUploadUrl()
.then(parseResponseUrl) // (response) => response.body.url
.then(url => uploadFiles(data, url))
.then(parseResponseUrl) // same function as above
.then(url => initializeSwam(data, url))
That should work just fine (or fine-ish, depending on what hand-waving you're doing in those functions).
partially apply your functions
const uploadFiles = (data) => (url) => {
return doOtherStuff(url, data);
};
// same deal with the other
const doStuff = data =>
getUploadUrl()
.then(parseResponseUrl)
.then(uploadFiles(data)) // returns (url) => { ... }
.then(parseResponseUrl)
.then(initializeSwam(data));
A mix of all of these techniques (when and where sensible) should be more than sufficient to solve a lot of your needs.
The way you have your code structured in the above snippet results in the getUploadUrl(), uploadFiles(), and initializeSwam() functions not being declared until the final .then(initializeSwam) call is made. What you have in this final .then() block is three function declarations, which simply register the functions in the namespace in which they are declared. A declaration doesn't fire-off a function.
I believe what you want is something like:
async function getUploadUrl() { <-- notice the flow control for Promises
const request = await superagent.get(....);
return request;
}
async function uploadFiles(responseFromGetUploadUrl) {
const request = await superagent.post(responseFromGetUploadUrl.body.url)
// attach files that are in data.files
return request;
}
async function initializeSwam(responseFromUploadFilesRequest) {
// Same thing here. I need access data and
repsonseFromUploadFilesRequest.body
const request = await ...;
}
module.exports = data => {
return getUploadUrl(data) <-- I'm guessing you wanted to pass this here
.then(uploadFiles)
.then(initializeSwam);
}
This approach uses ES6 (or ES2015)'s async/await feature; you can alternatively achieve the same flow control using the bluebird Promise library's coroutines paired with generator functions.