Search code examples
node.jsmongoosepromise

Cant access property of resolved promise using await Promise.all()


I have an array of images which I want to upload to MongoDB all at once:

images = [{ 
    id: 123,
    alt: "my image",
    buffer: <Buffer>,
    mimetype: "image/jpeg"
}]

I understand you should use Promise.all() for this, but I'm clueless about promises and was only able to string together the following where Image is my Mongoose Schema:

const uploaded_images = images.map(img => {
    return {
        id: img.id,
        promise: Image.create({
            image: { data: img.buffer, contentType: img.mimetype },
            alt: img.alt,
        })
    };
});

await Promise.all(uploaded_images.map(img => img.promise));

However, when I now try to access the _id property of the newly uploaded images, I am getting undefined

uploaded_images.forEach(img => {
    console.log(img.promise);        // gives Promise { { image: { data: new Binary(...), contentType: 'image/jpeg' }, alt: 'my image', _id: new ObjectId(...) } }
    console.log(img.promise._id);    // gives Undefined
})

How can I access the _id property?

After some more testing: Before the await Promise.all() line when I console log the array, I get [{ id: '013150333796129177', promise: Promise { <pending> } }]. Then after that line when I console log it, I get [{ id: '013150333796129177', promise: Promise { [Object] } }]. Does that mean it is resolved? Why cant I access the Object?


Solution

  • Waiting for all the .promise properties to resolve won't change them from promises to data; .promise will always be a Promise.

    I think you want something more like this...

    const uploaded_images = await Promise.all(
      images.map(async ({ alt, buffer, id, mimetype }) => ({
        id,
        image: await Image.create({
          alt,
          image: { data: buffer, contentType: mimetype },
        }),
      }))
    );
    
    uploaded_images.forEach((img) => {
      console.log(img.id); // original id
      console.log(img.image._id); // Image entity id
    });
    

    The async function used in the map() callback will return a promise that resolves when the Image.create() completes. Using Promise.all() on this array of promises will complete once they've all resolved.


    The above still inserts documents individually. If you want to batch-insert, you'd need to pass an array to Image.insertMany()

    const imageDocs = await Image.insertMany(
      images.map(({ alt, buffer, mimetype }) => ({
        alt,
        image: { data: buffer, contentType: mimeType },
      })),
      {
        ordered: true,
      }
    );
    
    const uploaded_images = imageDocs.map((image, i) => ({
      id: images[i].id,
      image,
    }));
    

    Note that combining the created docs with the id properties from the original array is a little more hacky now.