I'm lost with my async/awaits and promise chaining.
I am trying to create an array with images that will be stored on my server and then have their location passed as links to populate a MySQL database. However, the image creation confirmation/links aren't being returned before the DB save would be done. I think the problem is with my chained sharp commands in a different function, but I don't know how to make the main code wait on them.
Here is my controller code. I'm using Multer and memory storage to buffer the images uploaded. I use Express-Validator to Sanitize and Validate the code:
// saveTrail controller: [validation/sanitization, function]
exports.saveTrail = [
/* lots of validation on other parts of the code */
// Points of Interest validation
body().custom((value, { req }) => {
// Add the files back in to the req.body so that they can be treated normally in validation
const files = req.files.POI_image;
if (files) value.POI_files = files;
// Check to ensure all existing arrays are of the same length
return checkLengthOfObjectArrays(value, "POI");
}).withMessage("POI arrays must be the same length").bail().custom((value) => {
// reassemble the object array and attach it to the req.body (with file information)
value.POI = makeObjectArray(value, "POI");
return true;
}),
body("POI").optional(),
body("POI.*.files").exists().custom((value, { req }) => {
// check valid mime types
const mimetypeArr = value.mimetype.split("/");
return (
mimetypeArr[0] === "image" &&
VALID_IMAGE_TYPES.indexOf(mimetypeArr[1]) > -1
);
})
.withMessage(`Files must be of type: .${VALID_IMAGE_TYPES.join(", .")}`),
/* more validation/sanitization on the other POI fields */
// The actual function!
async (req, res) => {
try {
const body = req.body;
/* handle validation errors */
/* create the new trail */
// Make Point of Interest array
// images will be stored at /public/images/<Trail ID>/<POI || Hazard>/
if (body.POI) {
await body.POI.forEach(async (point) => {
const link = await getPOIImageLinks(trail.trailId, point.files);
point.trailId = trail.trailId;
point.image = link;
console.log("Point\n", point); // <-- this line shows up AFTER the response is sent
});
console.log("body.POI: ", body.POI); // <-- this shows the trailId, but not image, before the response is sent (This is where I would save to the DB)
}
res.status(201).json(body.POI);
return;
} catch (err) {
res.status(500).json({ error: err.message });
return;
}
},
];
// This is the function that manipulates the image and saves it
async function getPOIImageLinks(trailId, file) {
try {
// ensure filesystem exists for save
const path = SAVE_DIRECTORY + trailId + "/POI/";
await ensureDirExists(path); // <-- this shows up where it is expected
const { buffer, originalname } = file;
const { fileName } = getFileName(originalname);
const link = `${path}${fileName}.webp`;
await sharp(buffer)
.resize(RESIZE) // RESIZE is an imported settings object
.webp({ quality: 75 })
.toFile(link)
.then(info => {
console.log("Sharp info:", info); // <-- this shows up before the point output, but after the data has been resent. I think this is the location of my problem
})
.catch((err) => {
throw new Error(err);
});
return link;
} catch (err) {
console.log(err);
}
}
I think this has to do with the sharp.tofile()
command not waiting. I don't know how to break up that chain, the documentation isn't helping (https://sharp.pixelplumbing.com/api-constructor) and I just don't have enough experience switching between promise chains and async/await.
Thanks in advance!