Synchronize API calls in a for loops VueJS - javascript

I am trying to get synchronized flow inside a for loop which each loop having to call a API.
The flow is summarized as follows:
A Demo1() function containing forloop executes one by one, however in each loop it does a API call.,
A MainFunction() retrieves after the for loop has executed and does the final API call.
Supporting API calls for the same function is shown in code and seems quite self explanatory
The code structure is as follows:
</script>
...
async MainFunction() {
if (dataPresent) {
let clientFieldDetails = await this.Demo1()
.then(() => {
//This does the final API call based on the results fetched from for loop
this.processFinalAPICall();
})
.catch(err => {
console.log("Something went wrong... ", err);
});
} else {
console.log(
"No data.."
);
}
},
async Demo1() {
//Traversing around each fieldInfo
//this.dataPresent.items.forEach(item => {
//Replacing with normal for loop
for(item of this.dataPresent.items){
if (item.model) {
//Upload item model API Call
this.uploadItem(item.model)
.then(response => {
//PUT API Call
this.putItemModel().then(response => {
var result = response.data;
//Add itemModel fetched from API response
models.push(result);
console.log(result)
});
})
.catch(err => console.log("Axios err: ", err));
} else {
//Inside item price details
console.log("inside item price");
//API call for fetching price info
this.getitemPriceInfo(item.id);
}
});
},
getitemPriceInfo(itemid){
if(itemid){
//API call for fetching price info
this.getPriceinEuro(itemid);
itemsPrice.push(result)
}
else{
this.getPriceWorldWide().then(response => {
if(response.data === "item not created")
//Create new item API call
this.createNewItem(item.id)
else{
var result = response.data;
//Fetched API response for itemsPrice
itemsPrice.push(result);
console.log(result);
//Update item API call
this.updatePriceItem(item.id);
}
});
}
},
//EDIT: Adding API call
async getPriceinEuro(itemId) {
await this.$axios
.get("/auth/getEuroPrice", {
params: {
itemID: itemId
}
})
.then(response => {
console.log(" Resp :" + response.data);
let result = response.data;
//This gives me the price
itemsPrice.push(result)
});
},
processFinalAPICall(){
//get itemsPrice and models price pushed
var apiBodyModel = [];
this.models.forEach(model=>{
var uri = {
url: /modelInfo/+model
}
apiBodyModel.push(uri)
})
var apiBodyPrice = [];
this.itemsPrice.forEach(price=>{
var uri = {
url: /priceInfo/+price
}
apiBodyPrice.push(uri)
})
//Create a post body request from above data and POST
....
}
...
</script>
The code currently loops in for loop and doesnt wait for the API calls to finish. It executes processFinalCall() first and then the API calls. I am not sure about async/await, if I used it wrong please excuse. How do i get the the forLoop executed first and then the initiating the processFinalAPICall() from MainFunction?
Please let me know if I am doing it the right way. Thanks :)
I am using Node version 8.11 due to our project dependencies.
EDITED: Added API function Call for reference

I'm fairly sure that the problem you have lies in your Demo1 function, which, very broadly, looks like this:
async Demo1() {
[ARRAY OF ITEMS].forEach(item => {
this.uploadItem(item)
.then(response => {
// do some stuff
});
.catch(err => /* log errors */);
});
}
You don't await the upload here, so when you call Demo1() in MainFunction(), it'll go through the uploads without waiting for the previous one to finish first. I think the easiest way to get around this would be to use a for-of loop instead, since you pass a function to .forEach, and that just complicates things.
So instead of [ARRAY OF ITEMS].forEach(item => { ..., you can do this:
async Demo1() {
for(let item of [ARRAY OF ITEMS]) {
await this.uploadItem(item);
}
}
And your modified Demo1 function would look like this:
async Demo1() {
//Traversing around each fieldInfo
for (let item of this.dataPresent.items) {
if (item.model) {
//Upload item model API Call
await this.uploadItem(item.model)
//PUT API Call
let response = await this.putItemModel();
var result = response.data;
//Add itemModel fetched from API response
models.push(result);
console.log(result)
} else {
//Inside item price details
console.log("inside item price");
//API call for fetching price info
this.getitemPriceInfo(item.id);
}
};
}
Note that I haven't tested this, because I don't have the full code to plug it into, so I can't fully guarantee that it'll work. If it doesn't, let me know, so I can help fix it
Here's a bad mockup of this solution.

I guess its a simple change over from what #marsnebulasoup added in his mockup solution. From your comments I tried to replace upload method with axios call. You are missing return keyword after the function call , i.e,
upload(item) {
console.log("Iten value:"+ item);
return axios.get("https://jsonplaceholder.typicode.com/posts/" +item)
.then(response => {
console.log("Done processing API call (Times:"+ item)
console.log(response.data);
})
.catch(console.log);
A simple code changeover is designed based on previous answer by #marsnebulasoup in Replit:
EDIT: Added correct reference:
Code Reference: https://replit.com/join/comkahdm-gagangowda89
Cheers.

Related

How to slowdown making requests?

I have problem with too fast requesting. When my script makes too many requests per second Google throws an error net::ERR_INSUFFICIENT_RESOURCES
I want to make one request per 20ms.
How I can achieve that?
This is how my main function look at this moment...
const symbolsArr = reader.result.split("\n").map((str) => str.trim()); //symbolsArr is just rergular array, except it's source is from .txt file
function loopFunction(error_symbol) {
for (const symbol of symbolsArr) {
setTimeout(getData(symbol), 5000); //I tried to use setTimeout but it not helps
}
return console.log(error_symbol);
}
loopFunction(error_symbols);
And my fetcher...
error_symbols = [];
function getData(symbol) {
fetch(
`https://cors-anywhere.herokuapp.com/` +
`https://eodhistoricaldata.com/api/fundamentals/${symbol}?api_token= (don't look at my secret token :))`,
{
method: "get",
}
)
.then((res) => {
if (res.ok) {
return res.json();
} else {
throw new Error(`Symbol ${symbol} is 'empty'`);
}
})
.then((data) => {
console.log(data);
var myJSON = JSON.stringify(data);
saveFile(myJSON, `${symbol}-json.txt`, "text/plain");
})
.catch((error) => {
error_symbols.push(symbol);
throw error + symbol;
});
}
Pretty simple, I have to somehow put cooldown on fetcher
try this
const symbolsArr = reader.result.split("\n").map((str) => str.trim()); //symbolsArr is just rergular array, except it's source is from .txt file
async function loopFunction(error_symbol) {
for (const symbol of symbolsArr) {
await setTimeout(getData(symbol), 5000); //I tried to use setTimeout but it not helps
}
return console.log(error_symbol);
}
loopFunction(error_symbols);
the fetch api is using async promises to handle requests.
i am pretty sure u need to set a await to make the loop wait on each loopin till the fulfillment is done.

Loading data from Firebase asynchronously

I am developing a business manager web app in React with Firebase back-end. I am also coding a local API to simplify Firebase functions. I created this method, which loads data from a Firebase collection and returns an array of documents.
getDocuments(collection) {
var documents = [];
firebase.firestore().collection(collection).get().then(snapshot => {
snapshot.forEach(doc => {
documents.push(doc);
});
}).then(() => {
return documents;
})
}
However, when I call the method and assign it to a variable which I later print to the console, it says undefined.
var employees = getDocuments("employees");
console.log(employees);
What I want to do is to use .then() after calling the method to print the already loaded data to the console. Something like this:
var employees = getDocuments("employees").then(response => {
console.log(response);
})
Any help would be appreciated. Thank you.
Your getDocuments function seems to be unnecessarily complicated. This:
getDocuments(collection) {
return firebase.firestore().collection(collection).get().then(snapshot=>snapshot.docs)
}
yields exactly the same intended result (an Array of docs wrapped in a promise) as your function but performs faster since it skips looping through all the documents in the snapshot https://firebase.google.com/docs/reference/js/firebase.firestore.QuerySnapshot#docs
Afterwards just extract the value from the Promise returned by this function in your preferred way:
Option 1 (async/await)
let employees= await getDocuments('employees')
console.log(employees)
Option 2 (chaining)
let employees =[]
getDocuments('employees').then(response => {
employees=response
console.log(employees)
})
Explanation
When you are doing this:
var employees = getDocuments("employees").then(response => {
console.log(response);
})
you aren't receiving any value from getDocuments since you didn't return anything in the first place.
getDocuments(collection) {
var documents = [];
firebase.firestore().collection(collection).get().then(snapshot => {
snapshot.forEach(doc => {
documents.push(doc);
});
}).then(() => {
return documents; <-- this is returning the value of documents to the parent scope which is 'getDocuments', since the 'then' is related to the 'get' function
})
}
You should assign employees in the then like this
var employees = [];
getDocuments("employees").then(response => {
employees = response;
console.log(response);
})
Or if you are in an asynchronous function, you could go for something like this
var employees = await getDocuments("employees");
console.log(employees);
But the await keyword has to be done in an async function

How to parse JSON and store in an outside variable?

I simply want to load a JSON and store to a variable/array so that I could load the JSON api once and use the array to work with. Is there a way to fetch and store a JSON to a variable that I can access outside the fetch() or $.getJSON async functions?
I've come up with the code below, but I'm afraid it will load the JSON api everytime I call the function 'test'. Am I right?
function test(callback) {
$.getJSON('myURL', function (data) {
callback(data);
});
}
test(function (data) {
console.log(data);
});
If the data at myURL doesn't change, then one option is to have test cache the resolve value (or Promise), so that only one network request is made (and, for easy asynchronous use, use fetch, which returns a native Promise):
const test = (() => {
let prom = null;
return () => {
if (!prom) {
prom = fetch('myUrl').then(res => res.json());
}
return prom;
};
})();
test()
.then((data) => {
console.log(data);
});
You just need to declare the variable outside of the getJSON function:
var saved_data = null;
function test(callback) {
$.getJSON('', function (data) {
saved_data = data;
for(var i=0; i<saved_data.length; i++){
$('.list').append('<li data-id="'+i+'">'+saved_data[i].title+'</li>')
}
}, 1000);
}
test();
$(document).on('click', '.list li', function(){
console.log(saved_data[$(this).data('id')])
});
The first thing about asynchronous functions is that you have to wait for them to finish (and you don't know how long that can take), before you continue with your flow. In Javascript there are two ways to address that - using the Promise API or using a callback function. In your example you have a callback that's passed to getJSON, once that callback is fired, you will have the response data inside the saved_data variable.
If you populate the links in the callback, then you can be sure that whenever a link is clicked your saved_data variable will be available, holding the data from the API response.
JSFiddle
You can always assign the data to a variable outside of the callback. I usually do this inside of then().
const apiCall = new Promise(resolve => {
setTimeout(() => {
resolve('Data here')
}, 1000)
})
let result
apiCall.then(data => {
result = data
console.log(result)
})
And using async/await:
const apiCall = new Promise(resolve => {
setTimeout(() => {
resolve('Data here')
}, 1000)
})
const asyncFunction = async () => {
const result = await apiCall
console.log(result)
}
asyncFunction()

Removing nested api call from for loop

i made an api call to return a set of data(ex: users - list type, with the returned data i created a for loop and and within my for loop i make another api call to get the user's profile detail based on the user's id. I know this isn't the best practice and i was wondering how i could go about refactoring it.
api.get(...).then(response => {
this.users = response;
for(let i=0; i<this.users.length; i++){
api.get(...this.users[i].id).then(response => {
if(response.name == this.users[i].name)
this.newList.push(response);
})
}
})
and in my html i loop over this.newList to display the info that i need.
How can i remove the nested api call from within the for loop and still get the same results?
One possible solution is to use async/await. Although this will not remove nested loop, but make code look better
Example
async function getUsersAndProfiles () {
try {
this.users = await api.get(...);
for(let i=0; i<this.users.length; i++){
let response = await api.get(...this.users[i].id);
if (response.name == this.users[i].name)
this.newList.push(response);
}
}
catch (e)
console.log(e);
}
You can even move api call for user profile to another async function for possible future reuse and better code structure
Just push array of request into array after that we can use Promise.all() to make a request at a time, then we could create newList based on results array.
api.get(...).then(response => {
this.users = response;
const promises = this.users.map(user => api.get(...user.id))
const result = Promise.all(promises).then(result => {
this.newList = results.filter((user, i) => user.name === this.users[i].name)
})
})

Asynchronous method in while loop with Graph API paged

I'm using facebook node sdk for node.js to get information from a facebook user such as their feed and friends, which is working fine.
However I'm having an issue where the returned data is paged - I need to build something in recursive mode. Let me explain:
FB.api('/me/feed?limit=5000', {
access_token: token
}, function(response) {
// response is an object that could have as response.paging.next attribute
});
Limit isn't working here, because it returns a max of 245 and returns paging object indicating the next page of results.
Because the next call depends of the result of the previous async call, I tried to do something like this:
// first call before
var hasNext = response.paging.next ? true : false;
while (hasNext){
FB.api('/me/feed', {
access_token: token
}, function(response_paged) {
response.data.concat(response_paged.data);
// If do not have a next page to break the loop
if (!response_paged.paging.next) hasNext = false;
});
}
The way the next token was obtained is not important for now
The point is I'm trying to do async calls in recursive mode, but its not working, this way I'm getting an infinite loop.
My idea of solving this with async/await:
async function getFeed(token) {
let feedItems = [],
hasNext = true,
apiCall = '/me/feed';
while (hasNext) {
await new Promise(resolve => {
FB.api(apiCall, {access_token: token}, (response) => {
feedItems.concat(response.data);
if (!response.paging.next) {
hasNext = false;
} else {
apiCall = response.paging.next;
}
resolve();
});
});
}
return feedItems;
}
getFeed().then((response) => {
console.log(response);
});
Be aware that you need Node.js 7.9.0+ for this: http://node.green/
For older versions, install this: https://github.com/yortus/asyncawait
You can also use a recursive function, but the smooth/modern way would be async/await.
Instead of using:
feedItems.concat(response.data);
I have made: (if is the case on response.data has the data)
for(var i in response.data){
feedItems.push(response.data[i]);
}
As of today (12/10/21) response.data is of type Object. The below would now be appropriate for saving response data.
for (let d of response.data) {
feedItems.push(d)
}

Categories