Running a debounced function after user has navigated away from page - javascript

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

Related

How to stop a recursive setTimeout with an API call?

I have a NextJS application that runs a recursive setTimeout when the server is started. I need to create an API endpoint that can start and stop this loop (to have more control over it in production). This loop is used to process items in a database that are added from another API endpoint.
import { clearTimeout } from "timers";
var loopFlag = true;
export function loopFlagSwitch(flag: boolean) {
loopFlag = flag;
}
export async function loop() {
try {
// Retrieve all unprocessed transactions
const unprocessedTransactions = await prisma.transaction.findMany({
take: 100,
where: { status: "UNPROCESSED" },
});
// Loop through transactions and do stuff
for (const transaction of unprocessedTransactions) {
//stuff
}
} catch (e) {
// handle error
}
if (loopFlag === true) {
setTimeout(loop, 1000); //if flag changes, this will stop running
}
}
if (require.main === module) {
loop(); // This is called when server starts, but not when file is imported
}
The reason I use setTimeout and not setInterval is because many errors can occur when processing items retrieved from DB. These errors, however, are solved by waiting a few milliseconds. So, the benefit of the pattern below is that if an error happens, the loop immediately restarts and the error will not appear because a ms has passed (it's due to concurrency problems -- let's ignore this for now).
To attempt to start and stop this loop, I have an endpoint that simply calls the loopFlagSwitch function.
import { NextApiRequest, NextApiResponse } from "next";
import { loopFlagSwitch } from "services/loop";
async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
loopFlagSwitch(req.body.flag);
} catch (error) {
logger.info({ error: error });
}
}
export default handler;
Problem is, even when this endpoint is called, the setTimeout loop keeps going. Why isn't it picking the change in flag?
clearTimeout()
The global clearTimeout() method cancels a timeout previously established by calling setTimeout().
To clear a timeout, use the id returned from setTimeout():
Usage
const myTimeout = setTimeout(function, milliseconds);
//Then you can to stop the execution by calling clearTimeout():
clearTimeout(myTimeout);
loopFlag as a condition
...
if (loopFlag === true) {
myTimeout();
} else {
clearTimeout(myTimeout)
}
...
Add abortTimer function
Full code
export function loopFlagSwitch(flag) {
flag === true ? loop : abortTimer()
}
// set timeout
var myTimeout = setTimeout(loop, 1000);
function abortTimer() { // to be called when you want to stop the timer
clearTimeout(myTimeout);
}
export async function loop() {
try {
// Retrieve all unprocessed transactions
let d = "Retrieve all unprocessed transactions"
process.stdout.write(d + '\n');
// Loop through transactions and do stuff
for (let i = 0; i<10; i++) {
//stuff
let c = "second loop"
process.stdout.write(c + '\n');
}
} catch (e) {
// handle error
console.log("error ", e)
} finally {
myTimeout = setTimeout(loop, 1000); // repeat myself
}
}
if (require.main === module) {
loop(); // This is called when server starts, but not when file is imported
}
The flag will not work because node doesn't maintain the state of a file, the import only cares about the things it obtains from a file, it doesn't mind about the state of the variables declared in it.
Even though the clearTimeout() function may be sufficient, i think there is an even better option you can use to stop this loop.
Use a JS Class!
Instead of using just a function without state. You could instantiate a class that runs on the server with an internal boolean that can be called "shouldKeepLooping" for example:
class NextJsLooper { // or whatever better name you can use
private shouldKeepLooping: boolean
constructor () {
this.shouldKeepLooping = true
}
public shouldKeepLooping(value) { this.shouldKeepLooping = value }
public async loop () {
if (shouldKeepLooping) {
//... rest of the loop code
setTimeout(this.loop, 1000);
}
}
}
This way if you set the value to false it will automatically stop. Since this is a reference to the object.
Keep in mind that you would need to keep this instance alive as probably something global, and would need it to be accesible by nextJS.
You can use Symbol.for and the Node global to mantain the instance saved somewhere in the server!
My suggestion is to introduce the use of signals.
Service like Pusher will trigger the event that will be listened by the transaction processor.
your transaction processing api / code above or any other
Signal actions like "start" and "stop" will be triggered anytime even in production by either frontend or through the pusher portal that will be used to change the loop flag to either true or false.
you can retrieve the thread in charge of the operation and interrupt after a given expected time ,and log something to inform you about the time out in case that your operation took more than the necessary time .

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.

Cancel API call when modal closes - React

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.

Firebase Authentication detect if user came from redirect login

When using Firebase Authentication's firebase.auth().getRedirectResult() method, is there a way to detect if we should expect a result (or if the user came from a redirect login)?
There's a delay in when this method is run and when a success/error callback occurs. I'd like to show some loading state as soon as possible and don't see any methods in the documentation, like isRedirectResult.
For example, I'd like something like this (psuedo-code)
button.onclick = () => {
firebase.auth().signInWithRedirect(provider);
}
if (isFromRedirectPage) { // how do i do this??
button.loading = true;
await firebase.auth().getRedirectResult();
button.loading = false;
} else {
button.show();
}
Could be confusion around what the method was called, but I was able to use firebase.auth().onAuthStateChanged((user) => {...}) to solve this after all.
This method calls the callback function even if the user is not logged in, so I use that to determine loading state:
loading = true;
firebase.auth().onAuthStateChanged((user) => {
loading = false;
if (!user) {
// show login button
} else {
// hide login button
}
});

JS Promise waitFor refresh Token

The situation is simple :
I have a nodejs server (called it API-A) that use Bluebird as a Promise handler.
I have a client(browser) that ask data through the API-A which get the data from another API (API-B). API-B could be a Weather service from example and then API-A aggregate the data with other data and send it back to client.
The situation is the next: API-B need a token with a TTL of 1800 second.
So for each request done by the client, I check if my token is expired or not.
I have this kind of code :
function getActivities()
{
return this.requestAuthorisation()
.then(()=>{
// some other code that is not interesting
})
}
Everything works fine with the glory of promise.
requestAuthorisation() check if the token is valid (next!!!) and if not (I do a request the API-B to refresh the token)
The problem is here:
between the time, the token is expired and the time to obtain a fresh one, some times happen. if 1000 clients ask at the same time these, I will have 1000 request of token to API-B, that is not cool.
How can I avoid that ? I try to avoid a cron-way to do it, to avoid unnecessary call and the problem is the same.
I try to create a sort of global variable (boolean) that track the refreshing status of the token but impossible to find a sort of Promise.WaitFor (variable change)
the Promise.all can not be use because I am in different scope of event.
Is there a way to queue until the token is refresh ?
Please help !
If I understand this write, we need to do two things:
Do not call our refreshToken several times when one is in progress
Once completed, let all the waiting request know that request for the token in completed so that they can continue their work.
If you combine Observable pattern and a state to maintain the in-progress state, this can be done like below
// MyObservable.js:
var util = require('util');
var EventEmitter = require('events').EventEmitter;
let inProgress = false;
function MyObservable() {
EventEmitter.call(this);
}
// This is the function responsible for getting a refresh token
MyObservable.prototype.getToken = function(token) {
console.log('Inside getToken');
if (!inProgress) {
console.log('calling fetchToken');
resultPromise = this.fetchToken();
inProgress = true;
resultPromise.then((result) => {
console.log('Resolved fetch token');
inProgress = false;
this.emit('done', 'token refreshed');
});
}
}
// This is a mock function to simulate the promise based API.
MyObservable.prototype.fetchToken = function(token) {
console.log('Inside fetchToken');
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('resolving');
resolve("Completed");
}, 2000);
});
}
util.inherits(MyObservable, EventEmitter);
module.exports = MyObservable;
Now we can implement this and observe for the call to complete
const MyObservable = require('./MyObservable');
const observable = new MyObservable();
const A = () => {
console.log('Inside A');
observable.on('done', (message) => {
console.log('Completed A');
});
observable.getToken('test');
}
for (let i = 0; i < 5; i++) {
setTimeout(A, 1000);
}
If we run this code, you will get an output where fetchToeken is called only once even though our method A is called 5 times during the same duration.
Hope this helps!

Categories