I'm trying populate a response array with Firestore snapshot and inside each snapshot, create a download link of stored files. I tried solutions with Promises but always the response array was null.
docRef.get().then(async (snapshot: any) => {
await snapshot.docs.forEach(async (attachment: any) => {
await downloadFile(attachment.data()["paths"]).then((urls: any) => {
attachmentList.push({
"id": attachment.id,
"created_at": attachment.data()["created_at"],
"paths": urls,
"content_types": attachment.data()["content_types"]
})
}).catch(error => {
res.status(400).send({
"code": "ERROR",
"message": error
});
})
})
})
res.send({
"code": "ok",
"message": attachmentList
});
CodePudding user response:
This is a classic case for Promise.all
.
try this way
const snapshot = await docRef.get();
const results = await Promise.all(
snapshot.docs.map(
attachment => downloadFile(attachment.data()["paths"])
)
);
console.log(results);
CodePudding user response:
If we clean your indentation up, you can quickly see why you aren't getting back anything for "message" regardless of what you were trying in there - it returns the response before any of the asynchronous stuff even executes:
docRef.get()
.then(async (snapshot: any) => {
// <-- this line won't run
/* ... removed for conciseness */
});
res.send({
"code": "ok",
"message": attachmentList
});
// <-- before this line (and the response is sent already!)
What you are looking to do is to assemble your array of attachments, and then return them:
docRef.get()
.then((snapshot) => {
return Promise.all(
snapshot.docs.map(async (attachmentDocSnapshot) => {
const { paths, created_at, content_types } = attachmentDocSnapshot.data();
const urls = await downloadFile(paths);
return {
content_types,
created_at,
id: attachmentDocSnapshot.id,
paths: urls
};
})
);
})
.then((attachmentInfoList) => {
res.json({ // .json() is more semantic
"code": "OK", // for consistency, capitalized
"message": attachmentInfoList
});
})
.catch((err) => {
console.error(`Failed to collect information about the attachments for Doc #${docRef.id}: `, error);
res.status(500).json({ // 500 Internal Server Error
"code": "ERROR",
"message": error.code || error.message // Send back as little information about the error as possible
});
});
While the above code works, it's not pretty. So let's rework it into a child function:
async function getAttachmentInfoFromSnapshot(attachmentDocSnapshot) {
const { paths, created_at, content_types } = attachmentDocSnapshot.data();
const urls = await downloadFile(paths);
return {
content_types,
created_at,
id: attachmentDocSnapshot.id,
paths: urls
};
}
docRef.get()
.then((snapshot) => Promise.all(
snapshot.docs.map(getAttachmentInfoFromSnapshot)
))
.then((attachmentInfoList) => {
res.json({ // .json() is more semantic
"code": "OK", // for consistency, capitalized
"message": attachmentInfoList
});
})
.catch((err) => {
console.error(`Failed to collect information about the attachments for Doc #${docRef.id}: `, error);
res.status(500).json({ // 500 Internal Server Error
"code": "ERROR",
"message": error.code || error.message // Send back as little information about the error as possible
});
});
Note: You could rewrite the above to use async
/await
syntax on the next level up, but if you do, make sure to wrap it all in a try
/catch
block to handle any errors along the way.