Reputation: 538
I have this simple and short javascript code for a website:
db.collection("classes").doc(data.readGrades).get().then(function(doc) {
if (doc.exists) {
const data = doc.data();
const members = data.members;
members.forEach(el => {
db.collection("users").doc(el).collection("grades").get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
const data = doc.data();
//some stuff
});
});
})
} else {
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
As you can see I need to run two Firebase Firestore tasks. It is important that first this runs db.collection("classes").doc(data.readGrades).get().then(function(doc) {
and for each of the members this task follows db.collection("users").doc(el).collection("grades").get().then(function(querySnapshot) {
(NOTE: The second task needs the first one because of the var el
). So my problem is that I must wait to finish the second task before the loop for the next member could go on.
And exactly this is my question: how to complete the second task before running the loop for next member?
EDIT:
I modified my Javascript code like this:
db.collection("classes").doc(data.readGrades).get().then(function(doc) {
if (doc.exists) {
const data = doc.data();
const members = data.members;
members.reduce((chain, el) => {
table_number++;
const html = fillTemplate("grade_table" + table_number, member_name);
document.getElementById("main_padding").insertAdjacentHTML('beforeend', html);
return chain.then(() =>
db.collection("users").doc(el).collection("grades").get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
const data = doc.data();
addToTable("grade_table" + table_number, doc.id, data.mdl, data.klu);
});
})
)
}, Promise.resolve());
} else {
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
function fillTemplate(table_name, id) {
console.log("ID OF TABLES " + table_name);
return `
<div class="noten_tabelle_permission" id="noten_tabelle_permission">
<h1 id="member_name">${id}</h1>
<table id="${table_name}" style="width:100%">
<tr>
<th>Fach</th>
<th>mündlich</th>
<th>Klausur</th>
</tr>
<!-- Make content with js code -->
</table>
</div>
`;
}
function addToTable(table_name, subject, mdl, klu) {
var subject_name = getSubjectByNumber(subject);
var short_subject = getSubjectShortByNumber(subject);
//Zeile erstellen
console.log("addToTable " + table_name);
var y = document.createElement([short_subject]);
y.setAttribute("id", [short_subject]);
document.getElementById([table_name]).appendChild(y);
//Spalten in einer Zeile
var y = document.createElement("TR");
y.setAttribute("id", [short_subject]);
//Spalten in einer Zeile
var cE = document.createElement("TD");
var tE = document.createTextNode([subject_name]);
cE.appendChild(tE);
y.appendChild(cE);
var a = document.createElement("TD");
var b = document.createTextNode([mdl]);
a.appendChild(b);
y.appendChild(a);
var c = document.createElement("TD");
var d = document.createTextNode([klu]);
c.appendChild(d);
y.appendChild(c);
document.getElementById(table_name).appendChild(y);
}
So the promise works. But now I have the Problem that the promise works wrong. As you can see I want to run a Firestore(Database) task for every member. So the promise should fire every new member. But the promise is going to fire first at the end.
Background knowledge: I get the members from an ArrayList from the database and for every single member I need to get specific values based on that member. When the values for the member loaded I add them to a table, which is populated for every member(as you can see) in the previous task. As I said the promise is going to fire at the end. So the values would not be added to the specific table of the member. They are going to be added to the last table.
EDIT 2: This is the edited Code:
members.reduce((chain, el) => {
db.collection("users").doc(el).collection("user_data").doc("u").get().then(function (doc) {
const data = doc.data();
var member_name = data.name;
table_number++;
const html = fillTemplate("grade_table" + table_number, member_name);
document.getElementById("main_padding").insertAdjacentHTML('beforeend', html);
})
return chain.then(((table_num) => {
return db.collection("users").doc(el).collection("grades").get().then(function (querySnapshot) {
querySnapshot.forEach(function (doc) {
const data = doc.data();
addToTable("grade_table" + table_num, doc.id, data.mdl, data.klu);
});
})
}).bind(null, table_number))
}, Promise.resolve());
So the problem here is that the task where I get the member_name runs asynchronously and the other part not. So I only can run the whole task when everything is loaded. Because as you see I need also the member_name. SO how to recode it that the task run when only everything (member_name/grades) are loaded.
Upvotes: 3
Views: 284
Reputation: 7665
You can use reduce
instead of forEach
with an empty Promise
passed as an initial value in order to process members sequentially:
db.collection("classes").doc(data.readGrades).get().then(function(doc) {
if (doc.exists) {
const data = doc.data();
const members = data.members;
members.reduce((chain, el) => {
return chain.then(() =>
db.collection("users").doc(el).collection("grades").get().then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
const data = doc.data();
//some stuff
});
})
)
}, Promise.resolve());
} else {
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
Update #1:
Taking into account the updated question, the data is probably added to the last table because for the time of promises resolution all reduce iterations have been finished (and therefore table_name
variable keeps the value from the last iteration). You can solve it by binding
table_name
value to the callback invoked on the promise resolution:
members.reduce((chain, el) => {
table_number++;
const html = fillTemplate("grade_table" + table_number, doc.id);
document.getElementById("main_padding").insertAdjacentHTML('beforeend', html);
return chain.then(((table_num) => {
return db.collection("users").doc(el).collection("grades").get().then(function (querySnapshot) {
querySnapshot.forEach(function (doc) {
const data = doc.data();
addToTable("grade_table" + table_num, doc.id, data.mdl, data.klu);
});
})
}).bind(null, table_number))
}, Promise.resolve());
Update #2:
In order to run multiple asynchronous operations subsequently within a single reduce iteration try to chain them using then
method of a Promise:
members.reduce((chain, el) => {
return chain
.then(() => db.collection("users").doc(el).collection("user_data").doc("u").get())
.then(doc => {
const data = doc.data();
var member_name = data.name;
table_number++;
const html = fillTemplate("grade_table" + table_number, member_name);
document.getElementById("main_padding").insertAdjacentHTML('beforeend', html);
return table_number;
})
.then(table_num => {
return db.collection("users").doc(el).collection("grades").get().then(function (querySnapshot) {
querySnapshot.forEach(function (doc) {
const data = doc.data();
addToTable("grade_table" + table_num, doc.id, data.mdl, data.klu);
});
})
})
}, Promise.resolve());
Upvotes: 1
Reputation: 83058
Another approach would be to use Promise.all()
as follows:
db.collection("classes").doc(data.readGrades).get()
.then(doc => {
if (doc.exists) {
const data = doc.data();
const members = data.members;
const promises = []
members.forEach(el => {
const usersPromise = db.collection("users").doc(el).collection("grades").get();
promises.push(usersPromise);
});
return Promise.all(promises);
} else {
console.log("No such document!");
throw new Error("No such document!");
}
})
.then(results => {
results.forEach(querySnapshot => {
querySnapshot.forEach(doc => {
const data = doc.data();
//some stuff
});
});
})
.catch(error => {
console.log("Error", error);
});
The results
returned when the single Promise returned by Promise.all(promises)
resolves is an array with all the results of the promises, in the same order you pushed the promises in the promises
array.
Upvotes: 1