JS setInterval is being called multiple times - javascript

I'm having following code
if (input.isValidLink()) {
store()
.then(db => db.update(message.from, text, json))
.then(value => value.selectAllFromDB())
.then(users => makeRequest(users));
}
Amd makeRequest function
makeRequest(user) {
setInterval(() => {
users.forEach(user => {
httpRequest(user.json);
});
}, 10000);
}
What I'm trying to do, is selectAllFromDB function returns array of users from db, and it passed as argument to makeRequest function, which is looping thru each user, send request to receive json data and do it each 10 seconds, so it will not miss any changes. Each time when any user send the link, it should also start watching for this link. Problem is that on each received link it calls makeRequest function which creates another interval and if two links were received, I'm having two intervals. First looping thru this array
[{
id: 1234,
json: 'https://helloworld.com.json',
}]
And second thru this
[{
id: 1234,
json: 'https://helloworld.com.json',
}, {
id: 5678,
json: 'https://anotherlink.com.json',
}]
Is there any way this can be fixed so only one interval is going to be created?

You need to do something like this to make sure you only create one interval and are always using the latest users list:
let latestUsers;
let intervalId;
const makeRequest = (users) => {
latestUsers = users;
if (!intervalId) {
intervalId = setInterval(() => {
latestUsers.forEach(user => {
httpRequest(user.json);
});
}, 10000);
}
}
If you don't want variables floating around you can use a self-invoking function to keep things nicely packaged:
const makeRequest = (() => {
let latestUsers;
let intervalId;
return (users) => {
latestUsers = users;
if (!intervalId) {
intervalId = setInterval(() => {
latestUsers.forEach(user => {
httpRequest(user.json);
});
}, 10000);
}
}
})();

Related

How to use a callback to a setState or something similar

So this is the code I have been working on, it gets the data from API, then I assign the data to slides (using setSlides). After that I need to create a new array without the first one, but apparently slides is still null when it executes the setInterval. Which sounds not okay since then is only executed when the request is sucessful. Can anyone tell me what's wrong.
//---getting data from api
useEffect(() => {
axios
.get(DATA_URL, headers)
.then((answer) => {
setSlides(answer.data.spotlights);
let newArr = [...slides];
newArr.shift();
setTrio(newArr);
setSpolight(answer.data.spotlights[0]);
const interval = setInterval(() => {
switchSpolight();
}, 4000);
return () => clearInterval(interval);
})
.catch((answer) => console.log(answer));
}, []);
The solution to this problem is to move the code that relies on the updated value of slides into a separate function, and call that function inside the then block, after the setSlides function has been called.
For example:
useEffect(() => {
axios
.get(DATA_URL, headers)
.then((answer) => {
setSlides(answer.data.spotlights);
function updateTrio() {
let newArr = [...slides];
newArr.shift();
setTrio(newArr);
setSpolight(answer.data.spotlights[0]);
const interval = setInterval(() => {
switchSpolight();
}, 4000);
return () => clearInterval(interval);
}
updateTrio();
})
.catch((answer) => console.log(answer));
}, []);
I thing the problem is that you are trying to access the slides state right after the setSlides "which will not be available at that moment"
so I suggest you try this instead
before:
setSlides(answer.data.spotlights);
let newArr = [...slides];
after:
setSlides(answer.data.spotlights);
let newArr = [...answer.data.spotlights];

Sequential Promise All call with a variable param

I have a function
this.config.apiFunc = (pageNo) => this.somePaginatedCall({
page: {
number: pNo,
size: 10
}
})
Now, I want to fetch the data in a batch of 5 pages (by maintaining the sequence). I added a delay of 2000ms for the sake of testing. I created
config = {
apiFunc: any,
data: []
}
async getData(){
const pageGroupList = [
[1,2,3,4],
[5,6,7,8]
];
const groupedPromise = [];
groupedPromise.push(this.pageGroupList.map(pageNo => this.config.apiFunc(pageNo))); //<-- This is making network request
// because I am trigerring the function call with ()
await this.asyncForEach(groupedPromise,this.fetchInBatch.bind(this));
}
private asyncForEach(promiseList, func): Promise<any> {
return promiseList.reduce((p,apiList) => {
return p.then(this.sleep(2000)).then(() => func(apiList));
}, Promise.resolve());
}
private fetchInBatch(apiList) {
return Promise.all(apiList).then((res: any) => {
// this gets called after every 2 secs but I do not see any call in Network tab
this.config.data = [...this.config.data , ...[].concat(...res.map(r => r.data))];
})
}
sleep(ms) {
return (x) => new Promise(resolve => setTimeout(() => resolve(x), ms))
}
The problem is that I am making API request at groupedPromise.push(this.pageGroupList.map(pageNo => this.config.apiFunc(pageNo))) which I should not.
The data although loads as expected (after 2000 ms delay) but the network calls are already made.
I want to load the data after the 1st batch of pages is loaded (1,2,3,4) . In this example, after 2 secs.
Problem is that I want to pass pageNo to each API call before I invoke the function. I am slightly confused.
Try to do it like below. I have moved the map function inside the Promise.all
async getData(){
const pageGroupList = [
[1,2,3,4],
[5,6,7,8]
];
this.asyncForEach(pageGroupList,this.fetchInBatch.bind(this));
}
private asyncForEach(pageGroupList, execFunc) {
return pageGroupList.reduce((p,pageGroup) => {
return p.then(() => execFunc(pageGroup));
}, Promise.resolve());
}
private fetchInBatch(pageGroupList) {
return Promise.all(pageGroupList.map(pageNo => this.config.apiFunc(pageNo))).then((res: any) => {
this.config.data = [...this.config.data, ...[].concat(...res.map(r => r.data))];
})
}
I think your problem is that you're mapping the results of calling, but you should mapping functions, try this instead:
//Returns [fn1, fn2, ...];
groupedPromise.push(...this.pageGroupList.map(pageNo => () => this.config.apiFunc(pageNo)));
Or better:
async getData(){
const pageGroupList = [
[1,2,3,4],
[5,6,7,8]
];
const groupedPromise = this.pageGroupList.map(pageNo => () =>
this.config.apiFunc(pageNo)));
await this.asyncForEach(groupedPromise,this.fetchInBatch.bind(this));
}

Wait for executeScript to finish [duplicate]

This question already has answers here:
How do I convert an existing callback API to promises?
(24 answers)
Closed 1 year ago.
I have this script I execute from a Chrome extension
export const getData = () => {
chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
chrome.tabs.executeScript(
tabs[0]?.id,
{ code: `document.getElementById('a1').innerHTML;` }, parseData
);
});
};
const parseData = (result: any) => {
const obj = new Something();
// Do complex stuff with this object
chrome.storage.sync.set(
{
id: obj.id,
}
);
console.log('ID from extract', obj.id);
};
From my background script, I call this function and then call the function that gets the parsed data through chrome storage, so both of these functions take callbacks as arguments.
I need my first function to finish before executing the other one but it's completely ignoring the await, I'm not sure why and how can I make it wait, if it's even possible
background script
chrome.runtime.onMessage.addListener(async(request, sender, sendResponse) => {
let list = [];
await getData();
await getExtractedData((value) => {
// Do a lot of stuff with `list`
console.log('After extract:', value);
});
}
);
getExtractedData function is in another file:
export const getExtractedData = (callback) => {
return chrome.storage.sync.get(
[
'id',
],
callback);
};
It's supposed print this:
ID from extract 123456
After extract {id: 123456}
But it's currently doing this:
After extract {id: 123455}
ID from extract 123456
(After extract is logging what it extracted in the previous run, but not in the current run)
Functions need to return a Promise with a properly wired resolve/reject.
const getData = () => new Promise(resolve => {
chrome.tabs.executeScript({code: `document.getElementById('a1').innerHTML`}, res => {
parseData(res).then(resolve);
});
});
const parseData = (data) => new Promise(resolve => {
// Do complex stuff with this object
chrome.storage.sync.set({id: obj.id}, () => resolve(obj.id));
});
const getExtractedData = () => new Promise(resolve => {
chrome.storage.sync.get('id', data => resolve(data.id));
});
BTW you don't need storage as getData's Promise chain already resolves to the processed id:
async foo => {
const resultId = await getData();
// ..........
}

How to fix setTimeout behavior over HTTP requests?

On my client app, I'm using Socket IO to check for unread events. I make a request to my backend, which sets a timeout of 5 seconds, then proceeds to check for unread events and sends any back.
// client
socket.on("response", ({ mostRecentMessages }) => {
// do some stuff first
socket.emit("listenForNew", { userId, currentMessagesFromEveryone });
})
// backend
socket.on("listenForNew", ({ userId, currentMessagesFromEveryone }) => {
if (currentMessagesFromEveryone && userId) {
const { MostRecentMessages } = require("./constants/models");
const filteredIds = [];
currentMessagesFromEveryone.forEach(message => {
filteredIds.push(message.conversation._id);
});
console.log("Entered!");
setTimeout(async () => {
const mostRecentMessages = await MostRecentMessages.find({
to: userId,
date: { $gt: connectedUsersAllMessages[userId].timeIn },
conversation: { $nin: filteredIds }
}).populate("to from conversation");
allMessagesSocket.sockets.connected[
connectedUsersAllMessages[userId].socketId
].emit("response", {
mostRecentMessages
});
}, 5000);
}
});
At first, it works fine. It prints Entered! one time for about 4, 5 requests. Then on the 6th, it starts to print Entered! twice.
Why is this happening and what am I doing wrong?
I'm in favor of the below approach:
Wait for X seconds (5 in our use case)
Call an async operation (execution time in unknown)
Wait until async execution complete
Wait for another X seconds before executing the next call
Implementation may be something like that:
const interval = 5000;
function next() {
setTimeout(async () => myAsyncOperation(), interval);
}
function myAsyncOperation() {
const mostRecentMessages = await MostRecentMessages.find({
to: userId,
date: { $gt: connectedUsersAllMessages[userId].timeIn },
conversation: { $nin: filteredIds }
}).populate("to from conversation");
allMessagesSocket.sockets.connected[
connectedUsersAllMessages[userId].socketId
].emit("response", () => {
mostRecentMessages();
next(); // "next" function call should be invoked only after "mostRecentMessages" execution is completed (or a race condition may be applied)
});
}
next();
I haven't compiled this code but I hope that the concept is clear

How to clear interval placed inside .map callback function

I'm using map method on array in order to set intervals sending requests to API for a given number of times (each timeInterval have different access token).
Can I somehow create a function that will clear those intervals from outside?
await Promise.all(
this.state.tokens
.map((token, index) => {
const driver = setInterval(() => {
if (decodedPolylines[index].length > 1) {
api.sendLocation(
token,
decodedPolylines[index][0][0].toString(),
decodedPolylines[index][0][1].toString()
);
} else {
api.clockInOut(
token,
'out',
decodedPolylines[index][0][0].toString(),
decodedPolylines[index][0][1].toString()
);
clearInterval(driver);
}
}, 1000);
})
);
The function will clear all intervals, but you can also use filter() in case you want only some to be cleared:
const drivers = [];
await Promise.all(
this.state.tokens
.map((token, index) => {
const driver = setInterval(() => {
if (decodedPolylines[index].length > 1) {
api.sendLocation(
token,
decodedPolylines[index][0][0].toString(),
decodedPolylines[index][0][1].toString()
);
} else {
api.clockInOut(
token,
'out',
decodedPolylines[index][0][0].toString(),
decodedPolylines[index][0][1].toString()
);
clearInterval(driver);
}
}, 1000);
drivers.push(driver);
})
);
const clearDrivers = () => {
drivers.forEach(d => clearInterval(d));
};
// call clearDrivers() when you want to stop all intervals
You need to return those intervals first to be able to clear all of them:
const intervals = this.state.tokens
.map((token, index) => setInterval(() => {
...
}, 1000))
);
intervals.forEach(interval => clearInterval(interval));
Actually, i can't see any Promise in your code, are you sure you need to use await Promise.all(...)?

Categories