How to clear interval placed inside .map callback function - javascript

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(...)?

Related

How to stop a function from running in React if another function has returned its data

I have two functions that are calling two endpoints that pull data from a database. One function sometimes returns data quicker than the other.
// Function One
const getDataOne = async => {
await fetch('//some-api/one').then(res => res.json()).then(data => { setDataOne(date) }).catch(err => console.log(err))
}
// Function Two
const getDataTwo = async => {
await fetch('//some-api/two').then(res => res.json()).then(data => { setDataTwo(date) }).catch(err => console.log(err))
}
I have both functions written out separately like above and BOTH are fired at the same time via a onClick How can I stop the slower function from running if the quicker one has returned its data?
The data is populating tables, and if one table is populated before the other than there is no need to populate the slower table.
I know you can use return to exit out of a function, but not sure how we could use that to exit another function if one function has data returned before the other.
Could I do something like? ...
const getSegmentData = async (date, token) => {
getDataOne(token)
if (!getDataOne) {
getDataTwo(date, token)
}
}
Reason I am not a fan of the above, is because if getDataOne takes say 15 seconds...then getDataTwo would have to wait for 15 secs before running. Which would increase the users wait time.
Actual live code with Promise.Race
const getThirdPartyLiveRampSegments = async (date, token) => {
// resets setThirdPartyLiveRampSegments
setThirdPartyLiveRampSegments([])
setThirdPartyLiveRampSegmentsLoading(true)
let count = 0
const timer = setInterval(() => {
count++
setThirdPartyLiveRampSegmentsCount(count)
}, 1000)
if (token) {
try {
await fetch(
`//${
import.meta.env.VITE_BE_HOST
}/api/thirdparty-liveramp-segments?date=${date}&token=${token}`,
)
.then(res => res.json())
.then(data => {
setThirdPartyLiveRampSegments(data)
setThirdPartyLiveRampSegmentsLoading(false)
// clears token input to reuse with date input
setToken('')
// clear timer
clearInterval(timer)
})
} catch (error) {
console.log(error)
}
} else {
setThirdPartyLiveRampSegmentsLoading(false)
popupValidation('error', 'Please enter a segment id')
}
}
const getportalDboAdvpixel = async token => {
// resets setportalDboAdvpixel
setportalDboAdvpixel([])
setportalDboAdvpixelLoading(true)
let count = 0
const timer = setInterval(() => {
count++
setportalDboAdvpixelCount(count)
}, 1000)
if (token) {
try {
await fetch(
`//${
import.meta.env.VITE_BE_HOST
}/api/portal-dbo-advpixel?token=${token}`,
)
.then(res => res.json())
.then(data => {
setportalDboAdvpixel(data)
setportalDboAdvpixelLoading(false)
// clear timer
clearInterval(timer)
})
} catch (error) {
console.log(error)
}
} else {
setportalDboAdvpixelLoading(false)
popupValidation('error', 'Please enter a segment id')
}
}
const getSegmentDataRace = async (date, token) =>
await Promise.race([
getThirdPartyLiveRampSegments(date, token),
getportalDboAdvpixel(token),
])
value from queryRace is undefined...would I have to move all my states etc into the queryRace ?
I have removed the value...but still the slower function getThirdPartyLiveRampSegments is still running. When the others data has been populated.
The Promise.race method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects, with the value or reason from that promise.
const segmentData = await Promise.race([getDataOne(), getDataTwo()])
To stop the execution of the late function, you can use the JavaScript Promise.race() method with a state variable, as follows for example:
let state = 'idle'
const getDataOne = async => {
await fetch('//some-api/one').then(res => res.json()).then(data => {
if (state === 'idle') {
setDataOne(date)
state = 'success'
}
}).catch(err => console.log(err))
}
const getDataTwo = async => {
await fetch('//some-api/two').then(res => res.json()).then(data => {
if (state === 'idle') {
setDataTwo(date)
state = 'success'
}
}).catch(err => console.log(err))
}
const getSegmentData = async() => await Promise.race([getDataOne(), getDataTwo()])

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));
}

React script stop working after changing API call

I have a script which calls API from React and then triggers email notification function.
I was changing one part of it to call whole array of parameters instead of calling one parameter after another.
Here is part before change(working one). Console log shows correct response and I receive email notification as well.
const getApiData = () => {
const apiCall = (symbol) => {
return `https://min-api.cryptocompare.com/data/pricemulti?fsyms=${symbol}&tsyms=USD&api_key=API-KEY-HERE`
}
const MAX_CHARACKTERS = 300
let bucketArray = ['']
for (let i=0; i < assets.length - 1; i += 1) {
const symbol = `${bucketArray[bucketArray.length - 1]},${assets[i]}`
if (i === 0) {
bucketArray[0] = assets[i]
continue
}
if (symbol.length < MAX_CHARACKTERS) {
bucketArray[bucketArray.length - 1] = symbol
} else {
bucketArray[bucketArray.length] = assets[i]
}
}
const getData = () => {
Promise.all(
bucketArray.map(req => {
return axios(apiCall(req))
.then(({ data }) => data)
})
).then((data) => setDataApi(data))
}
getData()
};
Here is problematic one.
const getApiData = () => {
const getString = symbol =>
`https://min-api.cryptocompare.com/data/pricemulti?fsyms=${symbol}&tsyms=USD&api_key=API-KEY-HERE`;
function getAxious(id) {
const url = getString(id);
return axios.get(url);
}
const BUCKET_SIZE = 150;
const bucketArray = assets.reduce(
(arr, rec) => {
if (arr[arr.length - 1].length < BUCKET_SIZE) {
arr[arr.length - 1] = [...arr[arr.length - 1], rec];
return arr;
}
return [...arr, [rec]];
},
[[]]
);
bucketArray
.reduce((acc, rec) => {
return acc.then(results => {
return Promise.all(
rec.map(item =>
getAxious(item).then(({ data }) => {
return {
Symbol: item,
Open: data
};
})
)
).then(x => {
return [...x, ...results];
});
});
},
Promise.resolve([]))
.then(res => {
setDataApi(res);
});
};
Here in console I receive empty array - [] no errors showed, but email notification also stops from working.
I'm changing the code since I need to call whole array from API in one call. Before I was calling one symbol after another.
What I did wrong that console doesn't show the correct response?
EDIT1
Here is bucketArray value
const assets = ['ADA','KAVA','DOGE'];
I was not able to understand completely, but I think you want to collect all the results together and set it to the data using setDataApi.
Check the below code and let me know if it helps:
async function getApiData() {
const getString = (arr) =>
`https://min-api.cryptocompare.com/data/pricemulti?fsyms=${arr.join(
","
)}&tsyms=USD&api_key=API_KEY`;
function getAxious(arr) {
const url = getString(arr);
return axios.get(url);
}
const BUCKET_SIZE = 150;
const bucketArray = assets.reduce(
(arr, rec) => {
if (arr[arr.length - 1].length < BUCKET_SIZE) {
arr[arr.length - 1] = [...arr[arr.length - 1], rec];
return arr;
}
return [...arr, [rec]];
},
[[]]
);
const res = await getAxious(bucketArray);
console.log("res", res);
return res;
// after this you can set setDataApi(res);
}
// keep this useEffect sepearate
const [timer, setTimer] = useState(null);
useEffect(() => {
async function getApiDatahandler() {
const res = await getApiData();
console.log(res);
const timerId = setTimeout(() => {
getApiDatahandler();
}, 1000 * 60);
setTimer(timerId);
setDataApi(res)
// set the data setDataApi(res);
}
getApiDatahandler();
return () => {
window.clearTimeout(timer);
};
}, []);
// useEffect(() => {
// const timerId = setTimeout(() => {
// getApiData();
// }, 1000 * 60);
// }, [])
Checkout this codepen for a possible solution.
https://codepen.io/bcaure/pen/RwapqZW?editors=1011
In short, I don't know how to fix your code because it's quite a callback hell.
// Mock API and data
const bucketArray = [[{item: 'item1'}], [{item: 'item2'}], [{item: 'item3'}]];
const getAxious = item => {
return new Promise((resolve, reject) => resolve({data: 'API data'}));
}
// Return promise that combines API data + input item
const recToPromise = rec => rec.map(item => {
return new Promise((resolve, reject) => getAxious(item)
.then(data => resolve({item, data})));
});
// Flatten array
const recPromisesFlatten = bucketArray.flatMap(recToPromise);
Promise.all(recPromisesFlatten)
.then(res => {
const flattenRes = res.flatMap(({item, data}) => ({ Symbol: item, Open: data }));
console.log(JSON.Stringify(flattenRes))
});
What I'm suggesting to debug errors:
build your promise array first
then run Promise.all
then combine your data
Bonus: you can see flatMap instead of reduce for better readability.

How to make an api call inside of a map function with delay between each call on that api?

This second api call inside of the map function needs to be called in a space of time, because this api does not allow multiple calls at the time. So, the map for each item inside of the array will take two seconds to call the api and after it go to the next item.
How can i fix it?
It does not return anything.
async function HandleMatchList(){
try{
const responseMatches = await api.get('MatchListRankedGames', {
params: {
nickname
}
})
const matches = responseMatches.data
const Awaitfor2seconds = (x) => {
return new Promise (resolve => {
setTimeout(() => {
resolve(x)
}, 5000)
})
}
const linking = async (matches) => {
matches.map(async item => {
const details = await Awaitfor2seconds(
api.get('MatchDetailRoute', {
params: {
gameId: item.gameId,
nickname: nickname
}
}).then(({data}) => {
data
})
)
return details
})
}
linking(matches).then(results => {
setMatches(results)
})
}catch(e){
setError(e)
}
}
You can follow this concept (no tested):
const matches = responseMatches.data
var count = 0 // create a counter
const Awaitfor2seconds = (x) => {
return new Promise (resolve => {
count++ // count++ is the same thing that: count = count + 1
setTimeout(() => {
resolve(x)
}, 5000*count) // using this the request will be send like a queue
})
}
I suggest you make a sleep function separate and then you call it whenever you want to pause your API call
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
try{
const responseMatches = await api.get('MatchListRankedGames', {
params: {
nickname
}
})
const matches = responseMatches.data
await sleep(5000)
const linking = async (matches) => {
results=[]
for(let item of matches){
var details= await api.get('MatchDetailRoute', {
params: {
gameId: item.gameId,
nickname: nickname
}
})
results.push(details)
await sleep(5000)
}
return results
}
linking(matches).then(results => {
setMatches(results)
})
}catch(e){
setError(e)
}

JS setInterval is being called multiple times

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);
}
}
})();

Categories