0

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!

Chip
  • 137
  • 2
  • 13
  • 1
    Take a look at https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop – James Apr 04 '22 at 18:52
  • @James OMG! Thank you! I was totally exploring the wrong path for a solution! I never even considered the problem was with the `foreach`. – Chip Apr 04 '22 at 19:29

1 Answers1

0

James is correct the answer is at Using async/await with a forEach loop. I was totally chasing the wrong solution.

The problem was that I was calling this with a foreach loop. I needed to call my function with sharp with a for ... of loop.

     if (body.POI) {
        for (const point of body.POI) { // <--- the fix
          const link = await getPOIImageLinks(trail.trailId, point.files);
          point.trailId = trail.trailId;
          point.image = link;
          console.log("Point\n", point); // <-- this line shows up properly now
        }
     }  
Chip
  • 137
  • 2
  • 13