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));
}
Related
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();
// ..........
}
const getRecipes = async (flasks) => {
let newList = []
let recipes = flasks[0].recipes;
await Promise.all(recipes.map(async (recipe) => {
const response = await axios.get(`https://us.api.blizzard.com/data/wow/recipe/${recipe.id}?namespace=static-us&locale=en_US&access_token=${api_token}
`)
newList.push(response.data)
}))
// flasks[0].recipes.forEach(recipe => {axios.get(`https://us.api.blizzard.com/data/wow/recipe/${recipe.id}?namespace=static-us&locale=en_US&access_token=${api_token}
// `).then(response =>
// newList.push(response.data)
// )})
console.log(new Date())
console.log('logging newList')
console.log(newList)
return newList;
// setpotions_recipes(newPotions);
}
useEffect(() => {
axios.get(`https://us.api.blizzard.com/data/wow/connected-realm/104/auctions?namespace=dynamic-us&locale=en_US&access_token=${api_token}`).then(
response => {
const auctionList = response.data.auctions;
setAuctions(auctionList);
console.log('fetched auctions');
}
)
main();
}, [])
const main = async () => {
getRecipes(flasks).then( response => {
console.log('response:')
console.log(response)
getIngredientsList(response)
})
}
const getIngredientsList = (recipe_type) => {
let newList = new Map(ingredientsMap); //ingredientsMap is a state that holds information, this function tries to update ingredients (an array version of ingredientsMap)
console.log('In getIngredientsList');
console.log(recipe_type);
console.log(recipe_type.length);
recipe_type.forEach(item => {
console.log('inside recipe_type forEAch')
item.reagents.forEach(reagent => {
newList.set(`${reagent.reagent.name}`, {id: reagent.reagent.id, min: Infinity})
}
)
})
console.log(new Date())
setIngredientsMap(newList);
let newArray = Array.from(ingredientsMap, ([name, {id, min}]) => ({name, id, min}))
console.log(`logging ingredients from ${recipe_type}`)
console.log(newArray);
setIngredients(newArray);
}
Essentially I am trying to grab a set of items from an api, and then parse those items afterwards. I'm new to asynchronous functions so what I tried is to make getRecipes asynchronous and once that returns its list, call getIngredientsList on that response.
However, getIngredientsList logs recipe_type to be of length 0.
I believe this is because my asynchronous calls are not lining up correctly, but don't exactly know how to fix it.
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)
}
I'm going throgh the array of some data and on every iteration I deal with promise.
What I want is to go to the next forEach iteration only when the promise on the current iteration is resolved.
I searched through out different solutions and figured out that usage of for(... of ...) instead of forEach could make the trick. But still can't figure it out.
const data = ['apple', 'orange', 'banana'];
data.forEach((row, rowIndex) => {
let params = {
fruitIndex: rowIndex,
};
axios.post('/fruits', { ...params })
.then(response => {
// go to the next iteration of forEach only after this promise resolves
})
.catch(error => console.log(error))
});
Recursion helps:
const data = ['apple', 'orange', 'banana'];
function request_fruit(n) {
let params = {
fruitIndex: n,
};
axios.post('/fruits', { ...params })
.then(response => {
// Work with recived...
// ....
// Request next
if(n+1<fruits.length) request_fruit(n+1)
})
.catch(error => console.log(error))
};
request_fruit(0)
If you can, i'd recommed an easy to read and understand for of loop with await/async. Notice the top level await is very recent addition, so you should wrap that in some async function (which you'd likely do anyway).
If you cannot use async/await you can use reduce with an initial resolved promise to chain subsequent calls.
Notice that I used a function sleep that resolves a promise after some time. The call to sleep should be interchangeable with axios calls.
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
const data = [1000, 2000, 3000];
function withReduce() {
console.log('Sequential Promises with Reduce:');
return data.reduce((previousPromise, ms) => {
return previousPromise
.then(() => {
console.log(`sleeping for ${ms}ms`);
return sleep(ms);
});
}, Promise.resolve());
}
async function withAsync() {
console.log('Sequential Promises with for of and await:');
for (let ms of data) {
console.log(`sleeping for ${ms}ms`);
await sleep(ms);
}
}
withReduce().then(withAsync);
It looks like nobody posted the specific async/await yet, so this is how you use it with for…of:
const data = ['apple', 'orange', 'banana'];
for (const [rowIndex, row] of data.entries()) {
let params = {
fruitIndex: rowIndex,
};
let response;
try {
response = await axios.post('/fruits', { ...params });
} catch (error) {
console.log(error);
continue;
}
// …
}
This all being placed inside an async function. (Also, { ...params } is a bit weird. What’s wrong with params directly?)
Just use await on the api call.
function postFruitData(){
const data = ['apple', 'orange', 'banana'];
data.forEach(async (row, rowIndex) => {
let params = {
fruitIndex: rowIndex,
};
const response = await axios.post('/fruits', { ...params })
});
}
I have written a function that is being called in a loop (map) and that function is using promises. Now, I want that function to run synchronously and exit before its next instance is called.
function t1(){
let arr1 = [1,2,3,4,5];
return Promise.map(arr1, (val) =>{
const params = {
"param1" : val1
};
return t2(params);
});
}
function t2(event){
return Promise.resolve()
.then({
//do something
//code doesn't reach here in sync manner. all five instance are invoked and then code reaches here for first instance and so on
})
.then({
//promise chaining. do something more
})
}
t2 is beingcalled five times, but I want each instance to be called only after the instance before it has returned the value.
Currently its not behaving like that but invoking the function five times in parallel.
I can't use async/await due to project limitations.
The problem with your current code is that Promise.prototype.map, like forEach, does not wait for asynchronous functions called inside it to complete. (No asynchronous call will ever be waited for unless you tell the interpreter to do so explicitly with await or .then)
Have t1 await each call of t2:
async function t1(){
let arr1 = [1,2,3,4,5];
const results = [];
for (const val of arr1) {
results.push(await t2(val));
}
return results;
}
Or if you want to use reduce instead of async/await:
const delay = () => new Promise(res => setTimeout(res, 500));
function t1(){
let arr1 = [1,2,3,4,5];
return arr1.reduce((lastProm, val) => lastProm.then(
(resultArrSoFar) => t2(val)
.then(result => [...resultArrSoFar, result])
), Promise.resolve([]));
}
function t2(event){
return delay().then(() => {
console.log('iter');
return event;
});
}
t1()
.then(results => console.log('end t1', results));
Or, if you need the sequential functionality to be encapsulated in t2, then have t2 have a semi-persistent variable of the previous Promise it generated:
const delay = () => new Promise(res => setTimeout(res, 500));
const t1 = () => {
return Promise.all([1, 2, 3, 4].map(t2));
};
const t2 = (() => {
let lastProm = Promise.resolve();
return (event) => {
const nextProm = lastProm
.catch(() => null) // you may or may not want to catch here
.then(() => {
// do something with event
console.log('processing event');
return delay().then(() => event);
});
lastProm = nextProm;
return nextProm;
};
})();
t1().then(results => console.log('t1 done', results));
(function loop(index) {
const next = promiseArray[index];
if (!next) {
return;
}
next.then((response) => {
// do Something before next
loop(index + 1);
}).catch(e => {
console.error(e);
loop(index + 1);
});
})(0 /* startIndex */)
Here is what running Promises sequentially would look like when using .reduce() in combination with async/await:
async function main() {
const t2 = (v) => Promise.resolve(v*2)
const arr1 = [1,2,3,4,5];
const arr1_mapped = await arr1.reduce(async (allAsync, val) => {
const all = await allAsync
all.push(await t2(val) /* <-- your async transformation */)
return all
}, [])
console.log(arr1_mapped)
}
main()