React Axios API call with array loop giving wrong order? - javascript

I was learning react and doing some axios api call with an array. I did a code on gathering data through coinmarketcap api to learn.
So, my intention was to get the prices from the api with a hardcoded array of cryptocurrency ids and push them into an array of prices. But I ran into a problem with the prices array, as the prices were all jumbled up. I was supposed to get an array in this order
[bitcoinprice, ethereumprice, stellarprice, rippleprice]
but when I ran it in the browser, the prices came randomly and not in this order, sometimes I got my order, sometimes it didn't. I used a button which onClick called the getPrice method. Does anyone know what went wrong with my code? Thanks!
constructor(){
super();
this.state = {
cryptos:["bitcoin","ethereum","stellar","ripple"],
prices:[]
};
this.getPrice = this.getPrice.bind(this);
}
getPrice(){
const cryptos = this.state.cryptos;
console.log(cryptos);
for (var i = 0; i < cryptos.length; i++){
const cryptoUrl = 'https://api.coinmarketcap.com/v1/ticker/' + cryptos[i];
axios.get(cryptoUrl)
.then((response) => {
const data = response.data[0];
console.log(data.price_usd);
this.state.prices.push(data.price_usd);
console.log(this.state.prices);
})
.catch((error) => {
console.log(error);
});
}
}

If you want to receive the data in the order of the asynchronous calls you make, you can use Promise.all, that waits until all the promises of an array get executed and are resolved, returning the values in the order they were executed.
const cryptos = ['bitcoin', 'ethereum', 'stellar', 'ripple'];
const arr = [];
for (var i = 0; i < cryptos.length; i++){
const cryptoUrl = 'https://api.coinmarketcap.com/v1/ticker/' + cryptos[i];
arr.push(axios.get(cryptoUrl));
}
Promise.all(arr).then((response) =>
response.map(res => console.log(res.data[0].name, res.data[0].price_usd))
).catch((err) => console.log(err));

You could use a closure in the for loop to capture the value of i and use it as the index once the data is returned rather than using push:
getPrice(){
const cryptos = this.state.cryptos;
console.log(cryptos);
for (var i = 0; i < cryptos.length; i++) {
const cryptoUrl = 'https://api.coinmarketcap.com/v1/ticker/' + cryptos[i];
(function (x) {
axios.get(cryptoUrl)
.then((response) => {
const data = response.data[0];
console.log(data.price_usd);
var newPrices = this.state.prices;
newPrices[x] = data.price_usd;
this.setState({prices: newPrices});
console.log(this.state.prices);
})
.catch((error) => {
console.log(error);
});
})(i);
}
}

Related

async functions not executing in the correct order inside a map function

I have created an async function that will extra the data from the argument, create a Postgres query based on a data, then did some processing using the retrieved query data. Yet, when I call this function inside a map function, it seemed like it has looped through all the element to extra the data from the argument first before it proceed to the second and the third part, which lead to wrong computation on the second element and onwards(the first element is always correct). I am new to async function, can someone please take at the below code? Thanks!
async function testWeightedScore(test, examData) {
var grade = [];
const testID = examData[test.name];
console.log(testID);
var res = await DefaultPostgresPool().query(
//postgres query based on the score constant
);
var result = res.rows;
for (var i = 0; i < result.length; i++) {
const score = result[i].score;
var weightScore = score * 20;
//more computation
const mid = { "testID": testID, "score": weightScore, more values...};
grade.push(mid);
}
return grade;
}
(async () => {
const examSession = [{"name": "Sally"},{"name": "Bob"},{"name": "Steph"}]
const examData = {
"Sally": 384258,
"Bob": 718239,
"Steph": 349285,
};
var test = [];
examSession.map(async sesion => {
var result = await testWeightedScore(sesion,examData);
let counts = result.reduce((prev, curr) => {
let count = prev.get(curr.testID) || 0;
prev.set(curr.testID, curr.score + count);
return prev;
}, new Map());
let reducedObjArr = [...counts].map(([testID, score]) => {
return {testID, score}
})
console.info(reducedObjArr);
}
);
})();
// The console log printed out all the tokenID first(loop through all the element in examSession ), before it printed out reducedObjArr for each element
The async/await behaviour is that the code pause at await, and do something else (async) until the result of await is provided.
So your code will launch a testWeightedScore, leave at the postgresql query (second await) and in the meantime go to the other entries in your map, log the id, then leave again at the query level.
I didn't read your function in detail however so I am unsure if your function is properly isolated or the order and completion of each call is important.
If you want each test to be fully done one after the other and not in 'parallel', you should do a for loop instead of a map.

How to wait a loop in angular

I am new in angular and trying to do some loop operation in firebase. I want to fetch all the information by email id.
user_info = [];
for (let index = 0; index < this.email.length; index++) {
this.get_student_service
.get_student_by_email_id(this.email[index])
.subscribe((res) => {
this.user_info.push(res);
console.log(this.user_info);
});
}
console.log(this.user_info);
output:-
But I want print user_info after loop complete there execution
Fork Join will resolve your problem I guess, please try it out.
forkJoin(
this.email.map(email => this.get_student_service.get_student_by_email_id(email)
)
.subscribe(res => {
console.log(this.user_info); // will print result as an array
})
Instead of a for loop you could use the map method of the email array and then use an Observable.forkJoin to handle all the results at the same time.
Something like this:
import { forkJoin } from 'rxjs';
const getStudentsRequests = this.email.map(email =>
this.get_student_service.get_student_by_email_id(email);
);
forkJoin(getStudentsRequests).subscribe(resultArray => (this.user_info = resultArray));

Angular get response after subscribe loops end

thanks in advance for your time.
I am looping 2 services endpoints to get data, the problem that I am facing is that I would like to wait for the first subscribe to finish in order to loop the other one, but the result outside of the first subscribe is empty and if I place the second loop within the previous subscribe, I am facing duplications and/or undefined variables.
I have tried two ways
The first solution i have tried is looping the service endpoint and then looping the other service endpoint inside the first subscribe. The problem with this solution is that i am facing duplicates and/or undefinend variables.
The second solution i tried is using forkJoin, but i have two problems with this one.
The first problem i have using forkJoin is that i am getting a Pageable from my backend (using java - spring boot) to manage the pagination, so i cannot do a proper pagination because the forkJoin retrieve the Pagination in an Array, so the details are no longer useful, since it is splitted in the array.
The second problem i have using forkJoin is that everytime i click again to fetch the data it is adding the result to the old one, making endless duplicates (even when i set my arry to empty everytime i press the button)
I will share with you guys both examples i have tried and the service endpoints i am using
Service 1:
getAllQuestionsEvaluation(page: number, level: number, profu: number, idComponent: number): Observable<any> {
return this.http.get(this.urlEndpoint + 'page/' + page + '/' + level + '/' + profu + '/' + idComponent, {headers: this.headerService.addAuthHeader()}).pipe(
catchError((e) => {
this.headerService.serverError(e);
if (this.headerService.isNoAuth(e)) {
this.appDialogService.confirm();
}
return throwError(e);
})
);
}
I first loop that endpoint and then i need to loop the result for the next service to get the other data:
Service 2:
getAllByIdQuestion(idQuestion: number): Observable<any> {
return this.http.get(this.urlEndpoint + 'findbyidq/' + idQuestion, {headers: this.headerService.addAuthHeader()}).pipe(
catchError((e) => {
this.headerService.serverError(e);
if (this.headerService.isNoAuth(e)) {
this.appDialogService.confirm();
}
return throwError(e);
})
);
}
The first code i have been trying is the following, however i and facing duplicates and problems with the result:
jsonComponentsResult is the array result of components, however that service is not looping, so i am facing no problems with it and that is why i am not sharing that code.
this.questions = [];
this.questionsToCompare = [];
for (let com of jsonComponentsResult) {
this.questionService.getAllQuestionsEvaluation(0, this.evaluation.level, this.evaluation.profu, com.idComponent.idComponent).subscribe(
(jsonQuestions) => {
this.questionsToCompare = this.questionsToCompare.concat(jsonQuestions.content);
for (let i = 0; i < this.questionsToCompare.length; i++) {
this.questionsGradeService.getAllByIdQuestion(this.questionsToCompare[i].idQuestion).subscribe(
(response) => {
this.questionsGrades = this.questionsGrades.concat(response);
for (let p of this.questionsGrades) {
if (this.evaluation.idGrade.idGrade === p.idGrade.idGrade) {
this.questions = this.questions.concat(p.idQuestion);
}
}
this.dataSource = new MatTableDataSource < Questions > (this.questions);
}
);
}
this.pageIndex = jsonQuestions.number;
this.pageSize = jsonQuestions.size;
this.length = jsonQuestions.totalElements;
},
(error) => {
console.log(error);
}
);
}
My second solution is to use ForkJoin, however as i mentioned before, i am not able to manage my pagination and i am getting duplicates everytime i press the button even though i am setting the arrays to empty []:
observables: Observable < Questions > [] = [];
observablePG: Observable < QuestionsGrade > [] = [];
this.questions = [];
this.questionsToCompare = [];
this.questionsGrades = [];
for (let i = 0; i < jsonComponentResult.length; i++) {
this.observables.push(this.questionService.getAllQuestionsEvaluation(0, this.evaluation.level, this.evaluation.profu, jsonComponentResult[i].idComponent.idComponent));
}
Observable.forkJoin(this.observables).subscribe(
(response) => {
for (let x of response) {
this.questionsToCompare = this.questionsToCompare.concat(x);
}
this.observables = [];
this.getQuestions(this.questionsToCompare);
},
(e) => {
console.log(e);
}
);
private getQuestions(questionsArray: any) {
for (let i = 0; i < questionsArray.length; i++) {
this.observablePG.push(this.questionsGradeService.getAllByIdQuestion(questionsArray[i].idQuestion));
this.questions = [];
this.questionsToCompare = [];
}
Observable.forkJoin(this.observablePG).subscribe(
(response) => {
for (let x of response) {
this.questionsGrades = this.questionsGrades.concat(x);
}
this.observablePG = [];
for (let p of this.questionsGrades) {
if (this.evaluation.idGrade.idGrade === p.idGrade.idGrade) {
this.questions = this.questions.concat(p.idQuestion);
}
}
this.dataSource = new MatTableDataSource < Questions > (this.questions);
},
(e) => {
console.log(e);
}
);
}
I would like to wait for the first subscribe to finish in order to loop the other one, but i haven't been able to find a solution, since it looks like looping those services at the same time are causing troubles, and with the forkJoin i am losing my pagination and i am getting all the list again everytime i press the button.
I would like to get the questions list, then, get the questionsGrades to compare if i should add that question to my questions array.
Sorry for the long post and thank you for your time.
Maybe rxjs operator decision tree page could help you with this.
And the second, I remember that I had a similar issue, the only difference was that the value from the first Observable was used as a param in a second one, and for that I used switchMap operator.

Array remains empty after push

I declared an array, But when I push elements inside it, it remains Empty. Here's my Code :
var catsObjectId = new Array();
var data = new Array();
Recipe.find((err,doc3)=> {
data = doc3;
for (var i = 0; i < data.length; i++) {
catsObjectId.push([]);
data[i]['categories'].forEach((item, index) => {
Recipecat.findOne({_id: item}, (err,result)=> {
item = result.name;
catsObjectId.push(item);
});
})
}
console.log(catsObjectId);
});
Here's the Recipe schema :
var recipeSchema = Schema({
categories: [{
type: Schema.Types.ObjectId,
ref: 'RecipeCat',
}]
});
and Here's the Recipecat schema :
var recipecatSchema = new Schema({
name: {
type: String,
required: true
}
});
I want to replace objectIds for recipeCats with their names.
When I log 'catsObjectId', It shows an empty array.
What Seems to be the Problem?
Thanks In advance!
(I understand this question is a bit old, but if you still need help)
That's because you're pushing to an array which is outside the callback and the async nature of JavaScript kicking in.
Here's simple explanation why it's empty
var catsObjectId = new Array();
var data = new Array();
Recipe.find((err,doc3)=> {
// say execution 1
for (var i = 0; i < data.length; i++) {
catsObjectId.push([]);
data[i]['categories'].forEach((item, index) => {
// say execution 2
Recipecat.findOne({_id: item}, (err,result)=> {
item = result.name;
catsObjectId.push(item);
});
})
}
// say execution 3
console.log(catsObjectId);
});
First execution 1 is executed. Within this forEach iterates over each item and fires execution 2. Then continues to execute execution 3.
The problem is execution 2 is asynchronous and the value is returned sometime in the future. This future is after excution 3 is executed. When Recipecat.findOne finishes execution, the callback within then(result.. is called. But console.log(catsObjectId) is already executed and catsObjectId was empty at the time of execution.
You should either use catsObjectId within the callback .then((data) => // use data here) or use the async/await to make it sync like.
Note await is only valid inside async function
async function getSomeNames() {
try {
const data = await Recipe.find();
// docs is an array of promises
const docs = data.map((item, index) => {
Recipecat.findOne({_id: item})
});
// items is an array of documents returned by findOne
const items = await Promise.all(docs);
// now you can map and get the names
const names = items.map(item => item.name);
} catch (e) {
// handle error
console.error(e);
}
}
getSomeNames()
Your pushing an empty array every time it goes through the for loop. Try deleting this line.
catsObjectId.push([]);
You have to use promises in order to control your code. Try the following code and tell me if an error exists.
Recipe.find().then(doc3 => {
data = doc3;
for (var i = 0; i < data.length; i++) {
data[i]['categories'].forEach((item, index) => {
Recipecat.findOne({_id: item}).then(result => {
item = result.name;
catsObjectId.push(item);
});
})
}
console.log(catsObjectId);
})
.catch(err => {
console.log(err);
});
Recently ran into a similar problem. Fix for me was to replace the forEach loop with a simple for loop. It turned out, that the forEach loop is not bothering about async-await, but the for loop is.
Here is my code snippet:
let orders = await order_db.find({ profileID: req.body.id }).exec();
let final_orders = [];
for(let i=0; i<orders.length; i++){
let order = orders[i];
if (order.shopID != null) {
let shop = await shop_db.find({ _id: order.shopID }).exec();
let shopName = shop[0].name;
let shopEmail = shop[0].email;
let shopAddress = shop[0].address;
let shopPhone = shop[0].phone;
let updated_order = { ...order._doc, shopName, shopEmail, shopAddress, shopPhone };
final_orders.push(updated_order);
}
else {
let shopName = "";
let shopEmail = "";
let shopPhone = "";
let shopAddress = "";
let updated_order = { ...order._doc, shopName, shopEmail, shopAddress, shopPhone };
final_orders.push(updated_order);
}
};

Vue.js combine data from RESP API

I need combine multple API calls on a final object, this because API have limits to be consumed, any have an idea how is possible combine multiple calls in same final object, next is an example of my code, I need all data in this.lista but is not working:
created(){
this.$http.get('/api/transactions?senderId=8642612272713533685S&limit=1&offset=000')
.then( function(res){
console.log(res.body.count);
let limit = Math.ceil(res.body.count/1000);
console.log(limit);
let m = {};
let off = 0;
for (var i = 0; i <= limit; i++) {
this.$http.get('/api/transactions?senderId=8642612272713533685S&limit=1000', {params:{offset: off}})
.then( function(data){
this.lista = { ...this.lista, ...data.body.transactions }
} )
off = off + 1000;
}
}
);
}
any help will be appreciated
Using Promise.all is most likely what you are looking for. I will write just enough code for you to understand where to go.
// Populate your array array with URLs you want to get
let urls = ["url1", "url2", "...and so on"];
// Make into web request promises
let httpReqPromises = urls.map( url => this.$http.get(url) )
// Wait for all of them to resolve
Promise.all(httpReqPromises).then(allResponses => {
// Put them all together
this.lista = allResponses.reduce((a, b) => ({...a, ...b}, {})
})
The only work I leave up to you is how you populate the url variable.

Categories