I have a condition where I keep fetching API's every 2.5 seconds until the data at the path mentioned is resolved. I am using this inside a modal. The call is made every 2.5 seconds until the data is resolved. However, if the data isnt resolved and I plan to close the modal, the API call is still being made. How do I clear the timeout and stop the API call on close button click of modal? Please advice. Also, if the API isnt resolved in the 5th try, I would like to reject it .
const getData = (url, path) => {
return new Promise((resolve, reject) => {
try {
const loop = (async () => {
const result = await axios.get(url);
if (_.has(result.data, path) && result.data[path]) {
resolve(result.data[path]); // Resolve with the data
return; // Stop the loop
}
setTimeout(loop, 2500);
})();
} catch (e) {
reject(e)
}
});
}
(async() => {
const version = await getData('https://api.oceandrivers.com/static/resources.json', 'swaggerVersion');
console.log(version);
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
Where should I place the clearInterval? Please advice.
You should clear the timeout when the modal unmounts. You can do this by using the useEffect hook in the modal component and then do this:
const timeout = setTimeout(loop, 2500)
useEffect(()=>{return clear timeout(timeout)},[]);
Apologies for the code formatting, I'm on mobile.
Related
What im tryin to do is to get data from the backend. To do that I'm using axios.get().
I have created an useEffect without a dependency, because I want to call it every time.
The useEffect works, but when I get a null value as a response it stops, and if I add a new value (from postman) to test if the list will be updated but it won't. Only if I refresh the screen it will start getting the data list every second.
useEffect(() => {
console.log('called');
axios
.get(`API`)
.then((response) => {
setDataList(response.data.data); //null or data
})
.catch((error) => console.log('error', error));
});
How can I make the useEffect work even when I get a null value from response ?
By not putting a dependency array in place, you get the same effect as not using useEffect in the first place.
Every time the component renders, the Ajax request is triggered.
Your problem is that, all else being equal, the only time it will re-render is if setDataList(response.data.data) sets a different value.
If it errors, then it isn't setting any value so it won't trigger a re-render.
You need to decide what you really want to trigger a new request. For example, a timer.
(Untested code);
useEffect(() => {
let timeOut = null;
let controller = null;
const makeRequest = async () => {
controller = new AbortController();
const response = await axios.get(`API`, {
signal: controller.signal
});
const data = response.data?.data;
if (data) setDataList(data);
setTimeout(makeRequest, 5000); // Wait 5 seconds then poll again
};
makeRequest(); // Start the timer.
// Clean up on unmount
return () => {
clearTimeout(timeOut);
controller.abort();
};
}, [ /* You need a dependency array to stop multiple timers being created! */ ]);
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.
I have a debounced function of 3 seconds that I send off to an API service for tracking events.
// api.js
import { debounce } from 'lodash'
const submitRecords = debounce(async () => {
await API.submit({ ...data })
// do other stuff here
}, 3000)
Every time there's a user interaction, my application calls submitRecords, waits 3 seconds, and then makes a request to the API service. The problem here is that if the user navigates away before 3 seconds, the call never gets made.
Is there a way to still send the debounced request even when the user has navigated away from the current URL? I read up on window.onbeforeunload but I'm not sure if it's suitable for my use case.
Yes, you can use window.onbeforeunload.
But instead of async/await may be you need some another debounce implementation, or to do it by yourself.
It can be done with debounce implemented by usage setTimeout and storing timer somewhere globally.
In window.onbeforeunload check timer and if present - execute required logic.
Or you can try to use flag that indicates function in debouncing. Like:
const isDebouncing = false;
const submitRecords = () => {
isDebouncing = true;
debounce(async () => {
isDebouncing = false;
await API.submit({ ...data })
// do other stuff here
}, 3000)();
}
window.onbeforeunload = () => {
if (isDebouncing) {
// do request
}
}
Note: Besides just one flag you can store another data related to await API.submit({ ...data }).
Note: in some cases window.onbeforeunload requires preventing event and return value, like:
window.addEventListener('beforeunload', function (e) {
// Cancel the event
e.preventDefault();
// Chrome requires returnValue to be set
e.returnValue = '';
});
Described her: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload
I am new to async and maybe just am not wrapping my head around the fundamentals but I am trying to wait for user input from an onclick by calling an async function that pops a modal and waits for the user to submit data. After finding only one or two sources that even mentioned using async for waiting on page events that were not particularly helpful to my specific task... I came up with this:
asnyc func1 (){
var userInput = await async2();
//do stuff with user input
}
async func2(){
//build modal content specific to task
//display modal
return new Promise(function(resolve,reject){
$(document).on('click', '#save-input', function(e){
var input = $('#input-data').val();
resolve(input);
});
});
}
Everything seems to call correctly and i get the user input, but func1 never continues past the call to async2. So obviously im missing some crucial aspect of this but I can't seem to pull it from my sources.
Callback is not an option and there is a lot more to the code here than I can detail in short but the described above is the baseline functionality I need to perform.
Simple snippet
const timeout = async ms => new Promise(res => setTimeout(res, ms));
let next = false; // this is to be changed on user input
async function waitUserInput() {
while (next === false) await timeout(50); // pauses script
next = false; // reset var
}
Example usage with jQuery:
// just change the value of `next` for the script to continue
$('#user-input').click(() => next = true);
async function myFunc() {
// do stuff before
await waitUserInput(); // wait until user clicks
// do stuff after
}
myFunc() // launch function and start waiting for user input
Please see this working DEMO
// this is an async timeout util
const timeout = async ms => new Promise(res => setTimeout(res, ms));
let next = false; // this is to be changed on user input
let n = 1;
async function waitUserInput() {
while (next === false) await timeout(50); // pause script but avoid browser to freeze ;)
next = false; // reset var
}
async function myFunc() {
$('#text').append(`* waiting user input...<br>`)
await waitUserInput();
$('#text').append(`* user has clicked ${n++} time(s)<br>`)
myFunc()
}
$('#user-input').click(() => next = true)
myFunc()
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id='user-input' style='padding:15px;color:white; background: tomato; border: 0; border-radius:8px; font-weight: bold'>CLICK ME !</button>
<div id='text'>
</div>
Further improvements
This example could easily be improved to your needs. For example the variable next could also be used to store the user input value and the waitUserInput returns that value. That will lead to writing things like:
const userResponse = await waitUserInput(); // get user input value
// this is an async timeout util
const timeout = async ms => new Promise(res => setTimeout(res, ms));
let next = false; // this is to be changed on user input
async function waitUserInput() {
while (next === false) await timeout(50); // pause script but avoid browser to freeze ;)
const userInputVal = next;
next = false; // reset var
return userInputVal;
}
async function myFunc() {
$('#text').append(`* waiting user input...<br>`)
const userResponse = await waitUserInput();
$('#text').append(`* user choice is ${userResponse}<br>`)
myFunc()
}
$('#user-input').click(function() { next = $('#text-input').val() })
myFunc()
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<input id='text-input' type='text'/>
<button id='user-input' style='padding:15px;color:white; background: tomato; border: 0; border-radius:8px; font-weight: bold'>SUBMIT !</button>
<div id='text'>
</div>
I searched a lot but found nothing about it, just this question.
And I wanted something asynchronous without relying on loops to check, so I did a different solution.
The main problem was solving a promisse with an event listener, so I made the eventListener function inside the promisse and I use an arrow function to get the context of the promisse. Then I resolve the promise and cancel the eventListener after the first click.
And to make a function reusable with any listener I put an entry for that too.
function waitListener(element, listenerName) {
return new Promise(function (resolve, reject) {
var listener = event => {
element.removeEventListener(listenerName, listener);
resolve(event);
};
element.addEventListener(listenerName, listener);
});
}
It will return the event if you want to use it for something.
This makes it easier for you to write some sequence of codes in an orderly way. But to work it has to be inside an asynchronous function, which means that this function will start to run in parallel, so it is important to know where you are going to start in order not to hinder your process.
A simple use within some asynchronous function:
var element = document.querySelector("button");
await waitListener(element,"click");
A full sample:
function waitListener(Element, ListenerName) {
return new Promise(function (resolve, reject) {
var listener = event => {
Element.removeEventListener(ListenerName, listener);
resolve(event);
};
Element.addEventListener(ListenerName, listener);
});
}
async function awaitClicks(){
var element = document.querySelector("button");
await waitListener(element,"click")
// Do thing 1
.then(e=>{
console.log("e.clientX: "+e.clientX);
});
console.log(1);
await waitListener(element,"click");
// Do thing 2
console.log(2);
await waitListener(element,"click");
// Do thing 3
console.log(3);
}
awaitClicks();
<button>click</button>
If you need the same thing to happen every time you click a button, it is preferable to use just one addEventListener. And depending on what you are going to do you can use an addEventListener and cancel it already in the function it does using the removeEventListener. Now if you want to do something that depends on an order of events, maybe using this function can be a good solution.
Here:
import Rx from 'rxjs';
function fakeApi(name, delay, response) {
return new Rx.Observable(observer => {
console.log(`${name}: Request.`)
let running = true;
const id = setTimeout(() => {
console.log(`${name}: Response.`)
running = false;
observer.next(response);
observer.complete();
}, delay);
return () => {
if(running) console.log(`${name}: Cancel.`)
clearTimeout(id);
}
})
}
function apiSearch() { return fakeApi('Search', 4000, "This is a result of the search."); }
//============================================================
const messages$ = new Rx.Subject();
const toggle$ = messages$.filter(m => m === 'toggle');
const searchDone$ = toggle$.flatMap(() =>
apiSearch().takeUntil(toggle$)
);
searchDone$.subscribe(m => console.log('Subscriber:', m))
setTimeout(() => {
// This one starts the API call.
toggle$.next('toggle');
}, 2000)
setTimeout(() => {
// This one should only cancel the API call in progress, not to start a new one.
toggle$.next('toggle');
}, 3000)
setTimeout(() => {
// And this should start a new request again...
toggle$.next('toggle');
}, 9000)
my intent is to start the API call and stop it when it is in progress by the same toggle$ signal. Problem with the code is that toggle$ starts a new API call every time. I would like it not to start the new call when there is one already running, just to stop the one which is already in progress. Some way should I "unsubscribe" the outermost flatMap from toggle$ stream while apiSearch() is running. I guess that there is a need to restructure the code to achieve the behaviour... What is the RxJS way of doing that?
UPDATE: After some more investigations and user guide lookups, I came with this:
const searchDone$ = toggle$.take(1).flatMap(() =>
apiSearch().takeUntil(toggle$)
).repeat()
Works like it should. Still feels cryptic a little bit. Is this how you RxJS guys would solve it?
I think your solution will work only once since you're using take(1). You could do it like this:
const searchDone$ = toggle$
.let(observable => {
let pending;
return observable
.switchMap(() => {
let innerObs;
if (pending) {
innerObs = Observable.empty();
} else {
pending = innerObs = apiSearch();
}
return innerObs.finally(() => pending = null);
});
});
I'm using let() only to wrap pending without declaring it in parent scope. The switchMap() operator unsubscribes for you automatically without using take*().
The output with your test setTimeouts will be as follows:
Search: Request.
Search: Cancel.
Search: Request.
Search: Response.
Subscriber: This is a result of the search.