0

I am trying to upload three post images sent from frontend formData to Firebase Storage via express/node. Id like the function to return an array of objects, each with the image name and publicUrl that I can then save in a corresponding Post doc in Firestore.

The problem with current code below is that it returns an empty array []. The log in the urls.push() method displays the correct array though, I just cant figure out how to access it from the following .then() method.

Thanks for any help!

    router.post(
      "/",
      auth,
      uploader.fields([
        { name: "cardImage", maxCount: 1 },
        { name: "postImageOne", maxCount: 1 },
        { name: "postImageTwo", maxCount: 1 },
      ]),
    async (req, res, next) => {
    try {
      const { title, caption, content } = req.body;
      const { uid } = req.user;
      //add images to storage

      const imagesWriteFunction = async () => {
        var urls = [];

        for (const [key, value] of Object.entries(req.files)) {
          console.log(`---Starting upload process for ${key}---`);
          const blob = bucket.file(`${value[0].originalname}`);

          const blobWriter = blob.createWriteStream({
            metadata: {
              contentType: value[0].mimetype,
            },
          });

          blobWriter.on("error", (err) => next(err));
          blobWriter.on("finish", () => {
            console.log("---Assemblying Public URL---", blob.name);
            const publicUrl = `https://firebasestorage.googleapis.com/v0/b/${
              bucket.name
            }/ImagePosts/o/${encodeURI(blob.name)}?alt=media`;
            urls.push(`{ ${key}: ${publicUrl} }`);
            console.log("urls push log", urls);
          });
          console.log("urls .on() log", urls);

          blobWriter.write(value[0].buffer);
          blobWriter.end();
        }
        return urls;
      };

      imagesWriteFunction().then( (urls) => {
        console.log("iamgeUrls log", urls);
//I a want to access urls here so I can send with other form data to Post doc in firestore.
      });
      res.status(200).send(newPostData);
    } catch (err) {
      console.log("createPost error", err);
    }
  }
);

console.log

---Starting upload process for cardImage---
Sending image cardImage: [object Object] to Storage
---Starting upload process for postImageOne---
Sending image postImageOne: [object Object] to Storage
---Starting upload process for postImageTwo---
Sending image postImageTwo: [object Object] to Storage
urls log []
imagesWriteFunction urls log []
---newPostRes--- zt0OkMbLg2osceAtf3Vo
---Successfully added to user posts array--- WriteResult {
  _writeTime: Timestamp { _seconds: 1622789685, _nanoseconds: 86941000 }
}
---Assemblying Public URL--- woodywoodpecker.png
urls push log [
  '{ postImageOne: https://firebasestorage.googleapis.com/v0/b/devport-express-backend.appspot.com/ImagePosts/o/woodywoodpecker.png?alt=media }'
]
---Assemblying Public URL--- images.jpg
urls push log [
  '{ postImageOne: https://firebasestorage.googleapis.com/v0/b/devport-express-backend.appspot.com/ImagePosts/o/woodywoodpecker.png?alt=media }',
  '{ postImageTwo: https://firebasestorage.googleapis.com/v0/b/devport-express-backend.appspot.com/ImagePosts/o/images.jpg?alt=media }'
]
---Assemblying Public URL--- johnnybravo.jpg
urls push log [
  '{ postImageOne: https://firebasestorage.googleapis.com/v0/b/devport-express-backend.appspot.com/ImagePosts/o/woodywoodpecker.png?alt=media }',
  '{ postImageTwo: https://firebasestorage.googleapis.com/v0/b/devport-express-backend.appspot.com/ImagePosts/o/images.jpg?alt=media }',
  '{ cardImage: https://firebasestorage.googleapis.com/v0/b/devport-express-backend.appspot.com/ImagePosts/o/johnnybravo.jpg?alt=media }'
]
[nodemon] restarting due to changes...
[nodemon] starting `node index index.js`
Server started on port: 5000
3
  • blob.createWriteStream is an asynchronous function. Commented Jun 3, 2021 at 13:09
  • Thanks, but I am not sure how that affects the setting of my array? Commented Jun 3, 2021 at 13:18
  • I tried to await blob.createWriteStream().on() but await doesnt seem to have an effect on that type of expression. Commented Jun 3, 2021 at 14:01

1 Answer 1

1

Your for ... of loop is returning nothing because blobWriter is waiting for the event "finish" before filling your urls Array, so you leave the function before the action is done and you get nothing at the end. As you can clearly see on your log, first you got urls log [] imagesWriteFunction urls log [] and then you got the actual result of the blobWrite when the event "finish" get triggered.

To prevent this normal behavior you can use Promise to "promisify" this call and be able to await for it, here one way of doing it (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) :

const blobWriterAsync = (value, key) =>
  new Promise((resolve, reject) => {
    const blob = bucket.file(`${value[0].originalname}`);

    const blobWriter = blob.createWriteStream({
      metadata: {
        contentType: value[0].mimetype
      }
    });

    blobWriter.on('error', (err) => reject(err)); // <==== REJECT so you can catch the error later
    blobWriter.on('finish', () => {
      console.log('---Assemblying Public URL---', blob.name);
      const publicUrl = `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/ImagePosts/o/${encodeURI(blob.name)}?alt=media`;
      resolve(`{ ${key}: ${publicUrl} }`) // <== RESOLVE so you can await for it or use .then
    });

    blobWriter.write(value[0].buffer);
    blobWriter.end();
  });

const imagesWriteFunction = async () => {
  var urls = [];

  for (const [key, value] of Object.entries(req.files)) {
    try {
      const url = await blobWriterAsync(value, key)

      urls.push(url);
    } catch (err) {
      console.log("do something with err");
    }
  }
  return urls;
};
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, you did it! I was stuck on this for quite some time. And thanks for the explanation too.
I hope this concept is less blurry now ! Have a nice day

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.