Here are the steps to take:
Remove the await
keyword where you currently have it. So now subjects
will be an array of promises.
Add await Promise.all
in your push
call:
terms.push({
name: term._id,
subjects: (await Promise.all(subjects)).flat(),
});
That's it: now there will multiple pending promises until one item is pushed to the terms
array. This should already give some improvement.
If you want to have even more simultaneous pending promises, then also make terms
an array of promises:
terms.push(Promise.all(subjects).then(subjects => ({
name: term._id,
subjects: subjects.flat(),
})));
And then, after the outermost loop, have a one-for-all await:
terms = await Promise.all(terms);
Demo
I created some mock data along the structure that your code expects, with a mock getData
function, ...etc, so that code runs without errors. Here are the three versions of the code:
Original code
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const app = {
selectedGroup: {groups: ["A"],students: ["Student1", "Student2"]},
terms: [{assessments: [{year_groups: [{name: "A"}]}, {year_groups: [{name: "E"}]}],_id: 123},
{assessments: [{year_groups: [{name: "C"}]}],_id: 567}],
dataService: {
async getData(assessment, group, students) {
await delay(100);
return students.map(student => "++" + student);
}
},
async f() { // The function in the question (no change)
let terms = [];
this.selectedGroup.groups = ['A', 'B', 'C', 'D', 'E', 'F'];
for (let term of this.terms) {
let subjects = [];
//loop through each terms assessments
for (let assessment of term.assessments) {
//loop through the assessment year groups
for (let assessmentGroup of assessment.year_groups) {
//only get the assessments for the particular year group we are looking at
if (this.selectedGroup.groups.includes(assessmentGroup.name)) {
//get the subject data for this assessment
let newSubjects = await this.dataService.getData(
assessment,
assessmentGroup,
this.selectedGroup.students
);
//add the assessment subjects to the overall subjects array
subjects = subjects.concat(newSubjects)
}
}
}
//push all the subjects for the term into a terms array
terms.push({
name: term._id,
subjects: subjects,
});
}
return terms;
}
};
app.f().then(console.log);
With subjects
being promises
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const app = {
selectedGroup: {groups: ["A"],students: ["Student1", "Student2"]},
terms: [{assessments: [{year_groups: [{name: "A"}]}, {year_groups: [{name: "E"}]}],_id: 123},
{assessments: [{year_groups: [{name: "C"}]}],_id: 567}],
dataService: {
async getData(assessment, group, students) {
await delay(100);
return students.map(student => "++" + student);
}
},
async f() { // The function with first modification
let terms = [];
this.selectedGroup.groups = ['A', 'B', 'C', 'D', 'E', 'F'];
for (let term of this.terms) {
let subjects = [];
//loop through each terms assessments
for (let assessment of term.assessments) {
//loop through the assessment year groups
for (let assessmentGroup of assessment.year_groups) {
//only get the assessments for the particular year group we are looking at
if (this.selectedGroup.groups.includes(assessmentGroup.name)) {
// Removed `await`
let newSubjects = this.dataService.getData(
assessment,
assessmentGroup,
this.selectedGroup.students
);
//add the assessment subjects to the overall subjects array
subjects = subjects.concat(newSubjects)
}
}
}
//Wait for all subjects promises to resolve
terms.push({
name: term._id,
subjects: (await Promise.all(subjects)).flat(),
});
}
return terms;
}
};
app.f().then(console.log);
With subjects
and terms
being promises
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const app = {
selectedGroup: {groups: ["A"],students: ["Student1", "Student2"]},
terms: [{assessments: [{year_groups: [{name: "A"}]}, {year_groups: [{name: "E"}]}],_id: 123},
{assessments: [{year_groups: [{name: "C"}]}],_id: 567}],
dataService: {
async getData(assessment, group, students) {
await delay(100);
return students.map(student => "++" + student);
}
},
async f() { // The function with second modification
let terms = [];
this.selectedGroup.groups = ['A', 'B', 'C', 'D', 'E', 'F'];
for (let term of this.terms) {
let subjects = [];
//loop through each terms assessments
for (let assessment of term.assessments) {
//loop through the assessment year groups
for (let assessmentGroup of assessment.year_groups) {
//only get the assessments for the particular year group we are looking at
if (this.selectedGroup.groups.includes(assessmentGroup.name)) {
// Removed `await`
let newSubjects = this.dataService.getData(
assessment,
assessmentGroup,
this.selectedGroup.students
);
//add the assessment subjects to the overall subjects array
subjects = subjects.concat(newSubjects)
}
}
}
//push a promise for all subjects promises to resolve
terms.push(Promise.all(subjects).then(subjects => ({
name: term._id,
subjects: subjects.flat(),
})));
}
// One final spot to await all terms promises
terms = await Promise.all(terms);
return terms;
}
};
app.f().then(console.log);
As you can see the output is the same for each version.
But all of them output duplicate data: I don't know what your getData
does, but if it uses the students
argument, like I did, for building the result, then surely the result will have duplicate students. Similar duplication can occur because of the assessment
parameter, which also is repeated by the inner loop. As you said your code was correct, I leave that for you to further assess.