Asynchronous code inside an for each loop - javascript

This is my first post, because I usually find a way to answer my questions via the old messages... But this time I'm stuck. I hope you can help me.
searchUser.forEach(function(item, i) {
getUser(item)
.then(objUser => {
console.log(i);
console.log(objUser);
name = objUser.name;
let idConversation = objUser.id;
createNewConversation(name, idConversation)
})
})
My getUser is an asynch function, and I don't know why, but it seems that my construction does not work. it only creates the number of conversations i have but with the content of the last conversation and not the others... any idea of what I'm missing there? by the way console.log(i) gives 1 and then 0.
FYI the getUser is a function that returns data from the user.

Having an async function inside a forEach loop can be tricky to debug, i would suggest using Promise.all to call createNewConversation after all of the promises are resolved :
const promises = searchUser.map(item => getUser(item));
Promise.all(promises).then(result => {
result.forEach(({ name, id }) => {
createNewConversation(name, id);
});
});

Related

Data disappearing after async http calls in Angular

I have to code a tree component that displays multiple data, which worked fine with mocked data for me. The problem here is when I try to get data from servers, let me explain:
I have three main objects : Districts, buildings and doors. As you may guess, doors refers to buildingId and buildingID to districts. So to retrieve data and create my tree nodes, I have to do some http calls in forEach loops which is not asynchronous.
I won't share with you everything but just a minimized problem so I can get help easily:
This method retrieves a district array from the server and puts it in a local array:
async getDistricts(){
this.districtDataService.getDistrictData().toPromise().then(async districts => {
this.districts = await districts.results as District[];
});
}
On my ngOnInit :
ngOnInit() {
this.getDistricts().then(async () => {
console.log(this.districts);
for (const district of this.districts){
console.log(district);
}
})
The first console.log (in NgOnInit) returns an empty array, which is quite surprising because the first method puts the data in "this.districts". and logging data in the first method just after I put it in returns an array with my data. I guess it have something to do with the async/await I've used. Can anyone help?
EDIT 1: Tried to use this.getDistricts().finally() instead of this.getDistricts().then(), but didn't work.
EDIT 2: console.log in getDistrict get executed after the one before my loop. The expected behavior would be the opposite.
SOLVED: putting the for loop in a finally block after my HTTP call solves this. So as the answer says, I think I'm over engineering the async/await calls. I have to rethink my work based on this. Thank you everyone!
Well, you should return your Promise from getDistricts. Also you are very much over engineering and complicating the async/await concept. I understand you don't want to use Observables, but I would advise you to use them anyways.
With promises and async/await so you kinda see how to use them:
async getDistricts(): Promise<District[]> {
const { results } = await this.districtDataService.getDistrictData();
return results;
}
async ngOnInit(): Promise<void> {
this.districts = await this.getDistricts();
for (const district of this.districts){
console.log(district);
}
}
With Observable it would look like this:
getDistricts(): Observable<District[]> {
return this.districtDataService.getDistrictData().pipe(
map(({ results }) => results as District[])
);
}
ngOnInit(): void {
this.getDistricts().subscribe((districts) => {
this.districts = districts;
for (const district of this.districts){
console.log(district);
}
});
}
Just to provide whoever needs to make multiple http calls in a desired order.
as mentionned by others, i overcomplicated the concept of async await.
The trick was to use observables, convert them to Promises using .toPromise(), using .then() to get data into my variables, then making other async calls in finally block using .finally(async () => { ... }).
here's what my final code looks like :
async ngOnInit(): Promise<void> {
await this.districtDataService.getDistrictData().toPromise().then(response => {
this.districts = response.results as District[];
console.log(this.districts);
}).finally(async () => {
for (const district of this.districts){
await this.districtDataService.getBuildingsOfDistrict(district.id).toPromise().then(response => {
this.buildings = response.results as Building[];
console.log(this.buildings);
}).finally(async ()=> {
for(const building of this.buildings){
await this.districtDataService.getDoorsOfBuilding(building.id).toPromise().then(response => {
this.doors = response.results as Door[];
console.log(this.doors);
}).finally(async () => {
for(const door of this.doors){
await this.doorNodes.push(new districtNodeImpl(false,null,null,door,null));
}
})
await this.buildingNodes.push(new districtNodeImpl(false,null,building,null,this.doorNodes));
}
})
await this.dataSource.push(new districtNodeImpl(false,district,null,null,this.buildingNodes));
console.log(this.dataSource);
this.buildingNodes = new Array();
this.doorNodes = new Array();
}
})
Hope this will help ! have a nice day.

How can I access the value returned by a promise?

i'm kind of new to stackoverflow and i've been dealing for days with a problem. I have the next piece of code:
let comptot = function (value, data) {
return fetch(API_TOT)
.then(response => response.json())
.then((data) => {
let x = data[0].cantidad;
console.log(x);
return x;
})
.catch(error => {
console.log("el error es el siguiente", error)
})}
The problem is I can't access the value returned by it. It does log the value (230) to the console, but I want to display that value to a table (I'm using Tabulator), and it only returns:
Promise {<pending>}
__proto__: Promise
[[PromiseStatus]]: "resolved"
[[PromiseValue]]: "230"
I've read a bunch of quite similar questions but I can't figure out how to solve it. I also kind of understand how promises work, but obviously I didn't understood everything or I wouldn't be getting this problem (I also read a number of articles about promises and watched tens of youtube videos about promises and still nothing). I also tried using Async Await with the following code and had exactly the same problem:
let comtot = async function (value, data) {
let response = await fetch(API_TOT);
let com = await response.json();
console.log(com[0].cantidad);
return com[0].cantidad;
}
Please help me solve this, I would really apreciate it!
with this snippet,
let comptot = function (value, data) {
return fetch(API_TOT)
.then(response => response.json())
.then((data) => {
let x = data[0].cantidad;
console.log(x);
return x;
})
.catch(error => {
console.log("el error es el siguiente", error)
})
}
comptot references a Promise object as you mentioned, to extract its value use the .then() API,
comptot.then(res => {
console.log("Resolved value", res);
}).catch(err => {
console.log("Rejected value: ", err)
})
read more about promises and its .then() API
*This is the same answer for another question I posted. I didn't know what was wrong with my code so I posted two different questions for two things I thought were unrelated but i was wrong.
I figured it out, the problem was that in Tabulator, the mutator in the table was getting the data from another source than the one for the rest of the table, so the table was already built when the mutator started working, and what I figured out is that the mutator needs the data ASAP when it's fired, but it's source data wasn't ready yet, so what I did was to use:
function delayIt() {table.setData(API_URL)}
setTimeout(delayIt, 1000)
so the data from the mutator source was already available when the table was built so when the mutator was fired it all worked just fine. I'm sorry if it's confusing, I couldn't figure out how to explain other way.

Promise.all results are as expected, but individual items showing undefined

First of all, there are some issues with console.log in Google Chrome not functioning as expected. This is not the case as I am working in VSCode.
We begin with two async calls to the server.
promise_a = fetch(url)
promise_b = fetch(url)
Since fetch results are also promises, .json() will needed to be called on each item. The helper function process will be used, as suggested by a Stackoverflow user -- sorry lost the link.
let promiseResults = []
let process = prom => {
prom.then(data => {
promiseResults.push(data);
});
};
Promise.all is called. The resulting array is passed to .then where forEach calls process on item.json() each iteration and fulfilled promises are pushed to promiseResults.
Promise.all([promise_a, promise_b])
.then(responseArr => {
responseArr.forEach(item => {
process(item.json());
});
})
No argument is given to the final .then block because promiseResults are in the outer scope. console.log show confusing results.
.then(() => {
console.log(promiseResults); // correct results
console.log(promiseResults[0]); // undefined ?!?
})
Any help will be greatly appreciated.
If you are familiar with async/await syntax, I would suggest you not to use an external variable promiseResults, but return the results on the fly with this function:
async function getJsonResults(promisesArr) {
// Get fetch promises response
const results = await Promise.all(promisesArr);
// Get JSON from each response promise
const jsonResults = await Promise.all(results.map(r => r.json()));
return jsonResults
}
This is usage example:
promise_a = fetch(url1)
promise_b = fetch(url2)
getJsonResults([promise_a, promise_b])
.then(theResults => console.log('All results:', theResults))
Use theResults variable to extract necessary results.
You can try this, it looks the array loop is not going properly in the promise env.
Specifically: the promiseResults is filled after you are logging.
var resultAll = Promise.all([promise_a, promise_b])
.then(responseArr => {
return Promise.all(responseArr.map(item => return item.json()));
});
resultAll.then(promiseResults => {
console.log(promiseResults);
});

Firestore cloud functions summing subcollections values

sup guys, i'm working on this firebase project and i need to iterate trought a subcollection of all sales of all stores in the root collection and sum their values... the problem that i'm getting is that i'm getting the sum printed before the iteration. I'm new to TS and Firebase... this is what i got so far:
export const newBilling = functions.firestore.document('billings/{billId}').onCreate(event =>
{
const valueArray = []
const feeArray = []
const storesCollection = afs.collection('stores').where('active', '==', true).get().then(stores => {
stores.forEach(store => {
const salesCollection = afs.collection('stores').doc(store.id).collection('sales').get().then(sales => {
sales.forEach(sale => {
return valueArray.push(sale.data().value) + feeArray.push(sale.data().fee)
// other aproach
// valueArray.push(sale.data().value)
// feeArray.push(sale.data().fee)
})
})
})
}).catch(error => {console.log(error)})
let cashbackSum, feeSum : number
cashbackArray.forEach(value => {
cashbackSum += value
})
feeArray.forEach(value => {
feeSum += value
})
console.log(cashbackSum, feeSum)
return 0
})
TKS =)
You're not using promises correctly. You've got a lot of get() method call, each of which are asynchronous and return a promise, but you're never using them to case the entire function to wait for all the work to complete. Calling then() doesn't actually make your code wait - it just runs the next bit of code and returns another promise. Your final console.log is executing first because none of the work you kicked off ahead of it is complete yet.
Your code actually needs to be substantially different in order to work correctly, and you need to return a promise from the entire function that resolves only after all the work is complete.
You can learn better how to use promises in Cloud Functions by going through the video tutorials.

Axios API call returning Promise object instead of result?

Before I start, let me say that I'm new to Javascript and very new to axios API calls, so I'm probably making a rookie mistake...
I have this function getObjects() that's meant to map over an array and return the data from an Axios API call. The API call and map function are both working, but I'm getting a Promise object instead of the data I'm trying to get.
I figure this is because the data is returned before there's enough time to actually get it, but not sure how to fix? I tried a .setTimeout(), but that didn't seem to work.
getObjects() {
let newsItems = this.state.arrayofids.map((index) => {
let resultOfIndex = axios.get(`https:\/\/hacker-news.firebaseio.com/v0/item/${index}.json`).then((res) => {
let data = res.data;
//console.log(data.by); // this prints the correct byline, but
// all the bylines are printed after
// the console.log below...
if (!data.hasOwnProperty('text')) return data;
}); /// end of axios request
return resultOfIndex;
}); /// end of map
/// ideally this would end in an array of response objects but instead i'm getting an array of promises...
console.log(newsItems);
}
(The extra escape characters are for my text editor's benefit.)
Here's a link to a codepen with the issue - open up the console to see the problem. It's a React project but I don't think any of the React stuff is the issue. EDIT: Codepen is link to working solution using axios.all as suggested below
Thanks!
EDIT: Here is my working solution.
getObjects() {
let axiosArr = [];
axios.all(this.state.arrayofids.map((id) => {
return axios.get(`https:\/\/hacker-news.firebaseio.com/v0/item/${id}.json`)
})).then((res) => {
for (let i = 0; i < this.state.arrayofids.length; i++) {
axiosArr.push(<li key={i} data-url={res[i].data.url} onClick={(e) => this.displayTheNews(e)}>{res[i].data.title}</li>);
}
if (axiosArr.length == this.state.arrayofids.length) {
this.setState({arrayofdata: axiosArr});
console.log('state is set!');
}
})
}
axios.all function should be more appropriate to your current scenario.
Your console.log is executing immediately, rather than waiting for the requests to finish, because they are not synchronous. You have to wait for all the responses before you console.log.
OPTION 1 (the hard way):
replace your console.log with
newsItems.forEach((promise, index) => {
promise.then((object)=>{
newsItems[index] = object
if (index+1 == newsItems.length) {
console.log(newsItems)
}
})
})
OPTION 2 (the better way):
using axios.all
getObjects() {
axios.all(this.state.arrayofids.map((id) => {
return axios.get(`https:\/\/hacker-news.firebaseio.com/v0/item/${id}.json`)
})).then((res) => {
console.log(res)
})
}
by the way, I would definitely reccommend changing
this.state.arrayofids.map((index) => {
let resultOfIndex = axios.get(`https:\/\/hacker-news.firebaseio.com/v0/item/${index}.json`)...
to be called id instead of index

Categories