Promise chaining not returning expected result - reactjs

I have to send an e-mail for some users. I have a promise that returns the users Ids and another that returns the users e-mails (based on user id).
I have all of this chained but my parent function get an empty array.
I tried promises and async await but i have little experience with this and i dont know where im missing.
private async _getUserFromContatos(_subtipoEmergenciaID: number):Promise<string[]>{
const _arrTo: string[] = [];
sp.web.lists.getByTitle("Contato").items.get().then((items:any[]) => {
let _contatos = items.filter((i) => i.SubtipoEmergenciaId == _subtipoEmergenciaID);
_contatos.map(c => {
sp.web.getUserById(c.FuncionarioId).get().then(_userInfo => {
_arrTo.push(_userInfo.Email);
});
});
});
return _arrTo;
}
private _sendMail(){
this._getUserFromContatos(this.state.selectedSubtipoEmergencia).then(
_arrTo => {
console.log(_arrTo); //Returns the array as if its filled ok
console.log(_arrTo.length); //Returns 0 (empty array)
});
}
The first console.log at the end return the array filled but the second one returns 0.
I cant access the array items.

Your problem is that the array is not guaranteed to be filled when the first function returns. That is because you never await the result of the getUserById call.
Here are two possible solutions (one using await / one without await)
function _getUserFromContatos(_subtipoEmergenciaID: number): Promise<string[]> {
return sp.web.lists
.getByTitle("Contato")
.items
.get()
.then((items: any[]) => {
let _contatos = items.filter(i => i.SubtipoEmergenciaId == _subtipoEmergenciaID);
return Promise.all(
_contatos.map(c => {
sp.web
.getUserById(c.FuncionarioId)
.get()
.then(_userInfo => _userInfo.Email);
})
);
});
}
async function _getUserFromContatos(_subtipoEmergenciaID: number): Promise<string[]> {
var items = await sp.web.lists.getByTitle("Contato").items.get(); // await the list
let _contatos = items.filter(i => i.SubtipoEmergenciaId == _subtipoEmergenciaID);
var _arrTo: string[] = [];
for (var c of _contatos) {
var userInfo = await sp.web.getUserById(c.FuncionarioId).get(); // query every user and await that result
_arrTo.push(userInfo.Email);
}
return _arrTo;
}
function _sendMail() {
this._getUserFromContatos(this.state.selectedSubtipoEmergencia).then(
_arrTo => {
console.log(_arrTo); //Returns the array as if its filled ok
console.log(_arrTo.length); //Returns 0 (empty array)
}
);
}

Related

Recursively add promise await to a record key in typescript

I have record type alias schemaObj
const schemaObj: Record<string, any> = {};
I have a array of strings ref = ["abc.json", "test.json"]
I am iterating over ref and for individual element calling a recursive function and the return trying to assign to same record key. This is what I am doing
if (ref.length > 0) {
schemaObj[key] = await Promise.all(
_.map(ref, async (o) => {
return await recursiveFunction(o);
}).flat()
).then((values) => {
return values;
});
}
This serves the purpose, but I was more willing to do it in a chain sequence ( for better readability and cleaner code ). Especially on top of the same chain sequence where I am creating ref something like below
refSchemas = [{"values":"abc.json"},{"values": "xyz"},{ "values": "test.json"}]
_.filter(refSchemas, (o) => o.values.includes(".json"))
.map((e) => e.values)
.map(async (e:string) => {
schemaObj[key] = await recursiveFunction(e);
});
But this overrides the schemaObj[key] value. I tried the spread notation ... but didn't help.
map returns values in a loop, but you don't return them. Instead of that, you assign values back to schemaObj[key] in every round of map
A possible fix could be
schemaObj[key] = _.filter(refSchemas, (o) => o.values.includes(".json"))
.map((e) => e.values)
.map(async (e:string) => {
return await recursiveFunction(e); //return a value
});
You also can use a direct filter instead of a library's filter _.filter
schemaObj[key] = refSchemas.filter((o) => o.values.includes(".json"))
.map((e) => e.values)
.map(async (e:string) => {
return await recursiveFunction(e); //return a value
});

How could I write this function so it doesn't setState within the foreach everytime

The function collects role Assignment PrincipalIds on an item in SPO. I then use a foreach to populate state with the Title's of these PrincipalIds. This all works fine but it's inefficient and I'm sure there is a better way to do it than rendering multiple times.
private _permJeChange = async () => {
if(this.state.userNames){
this.setState({
userNames: []
});
}
var theId = this.state.SelPermJEDD;
var theId2 = theId.replace('JE','');
var info = await sp.web.lists.getByTitle('MyList').items.getById(theId2).roleAssignments();
console.log(info, 'info');
var newArr = info.map(a => a.PrincipalId);
console.log(newArr, 'newArr');
// const userIds = [];
// const userNames = [];
// const userNameState = this.state.userNames;
newArr.forEach(async el => {
try {
await sp.web.siteUsers.getById(el).get().then(u => {
this.setState(prevState => ({
userNames: [...prevState.userNames, u.Title]
}));
// userNames.push(u.Title);
// userIds.push(el);
});
} catch (err) {
console.error("This JEForm contains a group");
}
});
}
I've left old code in there to give you an idea of what I've tried. I initially tried using a local variable array const userNames = [] but declaring it locally or even globally would clear the array everytime the array was populated! So that was no good.
PS. The reason there is a try catch is to handle any SPO item that has a permissions group assigned to it. The RoleAssignments() request can't handle groups, only users.
Create an array of Promises and await them all to resolve and then do a single state update.
const requests = info.map(({ PrincipalId }) =>
sp.web.siteUsers.getById(PrincipalId).get().then(u => u.Title)
);
try {
const titles = await Promise.all(requests);
this.setState(prevState => ({
userNames: prevState.userNames.concat(titles),
}));
} catch (err) {
console.error("This JEForm contains a group");
}

Can't get first item in array Angular

I get some seasons of a series from my API.
After that, I want to use seasons[0] to get the first item in the array.
The problem is that seasons[0] returns undefined.
My Code looks like this :
async ionViewWillEnter() {
const seasons = await this.serieService.fetchCompleteSerie(this.serie);
this.seasons = seasons;
console.log(seasons); //output below
console.log(seasons[0]); //undefined
this.selected = seasons[0]; //undefined
}
my service looks like this:
async fetchCompleteSerie(serie: Serie) {
let allSeasons: any;
let serieSeasons = [];
let allEpisodes: any;
let seasonEpisodes: any;
allSeasons = await this.http.get('https://www.myapp.com/api/seasons/', this.httpOptions).toPromise();
await allSeasons.forEach(async season => {
season["episodes"] = [];
if (season.serie === serie.url) {
allEpisodes = await this.http.get('https://www.myapp.com/api/episodes/', this.httpOptions).toPromise();
allEpisodes.forEach(episode => {
if (episode.season === season.url) {
season.episodes.push(episode);
}
});
serieSeasons.push(season);
}
});
return serieSeasons;
}
The console output looks like this :
Why is it undefined?
The problem is the forEach which DOES NOT RETURN the promises you try to wait for. For that reason seasons[0] is still undefined. But since you log the array to the console and THE SAME array object is used inside your callback, the console refreshes the output after the data arrives. If you clone the array before logging, you will see that its empty console.log([...seasons]);
Simply switch forEach to map and use Promise.all.
async fetchCompleteSerie(serie: Serie) {
let allSeasons: any;
let serieSeasons = [];
let allEpisodes: any;
let seasonEpisodes: any;
allSeasons = await this.http
.get("https://www.myapp.com/api/seasons/", this.httpOptions)
.toPromise();
await Promise.all(allSeasons.map(async season => {
season["episodes"] = [];
if (season.serie === serie.url) {
allEpisodes = await this.http
.get("https://www.myapp.com/api/episodes/", this.httpOptions)
.toPromise();
allEpisodes.forEach(episode => {
if (episode.season === season.url) {
season.episodes.push(episode);
}
});
serieSeasons.push(season);
}
}));
return serieSeasons;
}

How to Verify if each element the Array contains the search string in Typescript/protractor

How do i Verify if each element of the Array contains the search string in Typescript/ Protractor??
All the console statements returned false as they were looking for complete text rather than a search string. Please suggest a solution.
arr = [ 'Citibank, N.A.', 'Citi China Companies', 'Citibank Ireland' ]
search string = 'citi'
Then('I enter search text where the highlighted search results will include a Client Company Name {string}, {string}', async (searchText, companyName) => {
await acctmgrclientselection.deleteSearchText().then(async () => {
await acctmgrclientselection.getSelectClientSearchInputEl().sendKeys(searchText).then(async () => {
await acctmgrclientselection.getSelectClientSearchInputEl().sendKeys(protractor.Key.ENTER).then(async () => {
await dashboardFilter.getEmployeeListGrid().count().then( async ( CountVal ) => {
if(CountVal >1)
{
var strArr: Array<string> = [];
await acctmgrclientselection.getClientTblCompanyName().getText().then(async (text) => {
await strArr.push(text)
//strArr.forEach(function(value){
var sortable = [];
strArr.forEach(value => {
sortable.push([value]);
let sorted_array: Array<string> = sortable.map(arr => arr[0])
let result = sorted_array.every(element => element.includes(searchText))
console.log(result)
});
});
}
else
{
//clear criteria
console.log('clear criteria');
await element(by.cssContainingText('mat-card.empty-results.mat-card p','0 results matching your criteria')).isDisplayed().then(async()=>{
await element(by.cssContainingText('mat-card.empty-results.mat-card a','Clear Criteria')).isDisplayed();
});
}
});
});
});
});
});
You need to check for the regex with search string.
const pattern = new RegExp("Citi");
const result = sorted_array.every(element => pattern.test(element));
console.log(result);

Parsing firebase query data in reactjs

I am using firebase cloud firestore for storing data. And I am developing a web app using reactjs. I have obtained documents using the following function:
getPeoples() {
let persons = [];
firestore.collection("students")
.get()
.then(function (querySnapshot) {
querySnapshot.forEach((doc) => {
var person = {};
person.name = doc.data().Name;
person.course = doc.data().Course;
persons.push(person);
})
});
console.log(persons);
return persons;
}
I am getting the desired data, but when I am iterating through persons array, it says it has length of 0.
here is the console output when I am displaying complete persons array and its length.
The length should be 14, but it shows 0. Please correct me what is wrong with me?
I want to display the output in the html inside the render() method of react component.
The output of
const peoples = this.getPeoples();
console.log(peoples);
It is:
The complete render method looks like:
render() {
const peoples = this.getPeoples();
console.log(peoples);
return (
<div className="peopleContainer">
<h2>Post-Graduate Students</h2>
{/* <h4>{displayPerson}</h4> */}
</div>
);
}
This is due to the fact the query to the database is asynchronous and you are returning the persons array before this asynchronous task is finished (i.e. before the promise returned by the get() method resolves).
You should return the persons array within the then(), as follows:
getPeoples() {
let persons = [];
return firestore.collection("students")
.get()
.then(function (querySnapshot) {
querySnapshot.forEach((doc) => {
var person = {};
person.name = doc.data().Name;
person.course = doc.data().Course;
persons.push(person);
})
console.log(persons);
return persons;
});
}
And you need to call it as follows, because it will return a promise :
getPeoples().then(result => {
console.log(result);
});
Have a look at what is written to the console if you do:
console.log(getPeoples());
getPeoples().then(result => {
console.log(result);
});
I'm not sure but please try to update your
getPeoples() {
let persons = [];
firestore.collection("students")
.get()
.then(function (querySnapshot) {
querySnapshot.forEach((doc) => {
var person = {};
person.name = doc.data().Name;
person.course = doc.data().Course;
persons.push(person);
})
});
console.log(persons);
return persons;
}
to
getPeoples() {
let persons = [];
firestore.collection("students")
.get()
.then(querySnapshot => {
querySnapshot.forEach((doc) => {
persons.push({name = doc.data().Name,
course = doc.data().Course
})
});
console.log(persons);
return persons;
}
Update
Sorry I thought you have problem with filling persons array in your function. Anyway as Renaud mentioned the query in your function is asynchronous so the result is not quick enough to be displayed on render. I use similar function and I found redux the best way to handle this situations.

Resources