8

Here is my code:

exports.propertyById = async (req, res) => {
    try {
        const {propertyId} = _.get(req, 'params'),
        propertyData = await bService.getPropertyById(propertyId);
        console.log(propertyData);
        const propertyPhotoList = [];
        async function getPhotoData(item, index){
            const id = item.split('#')[1];
            const response = await fetch(`http://localhost:4000/api/propertyphoto/${id}`);
            const body = await response.json();
            console.log(body);
            propertyPhotoList.push(body);
        }
        propertyData.PropertyPhotos.map(getPhotoData);
        console.log(propertyPhotoList);
        return res.success(res, propertyData);
    } catch (err) {
        return res.error(res, err.response.status || 500, err.response.statusText || err);
    }
}

What's confusing me it that the 'console.log(body)' inside the asynchronous function 'getPhotoData' is returning the JSON object perfectly fine.

But the array outside of the asynchronous function 'getPhotoData' is still returning as empty, '[]'.

I am unsure whether the object is not being successfully being pushed, or if this is some sort of issue with async/await. I am coming from callbacks so this is still new to me.

I am using Node.js v8.12.0 on Ubuntu 18.10.

3 Answers3

8

Two problems:

  1. You shouldn't be using .map for side effects. It returns a new array so you should make use of that.

  2. .map doesn't know anything about async functions. All you are doing is creating an array of promises. When .map and your function returns, the promises are not "done" yet. You need to await all of them.

With that said:

async function getPhotoData(item, index){
    const id = item.split('#')[1];
    const response = await fetch(`http://localhost:4000/api/propertyphoto/${id}`);
    return await response.json();
}
const propertyPhotoList = await Promise.all(
    propertyData.PropertyPhotos.map(getPhotoData)
);
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • You saved my day today, man. Spend a few hours trying to figure out how to achieve such behavior. And in the end it is so simple. Thanks *cheers* – Ellrohir Sep 21 '22 at 11:38
5

You need to use Promise.all and await:

await Promise.all(propertyData.PropertyPhotos.map(getPhotoData));

Here's the complete code with the fix:

exports.propertyById = async (req, res) => {
    try {
        const {propertyId} = _.get(req, 'params'),
        propertyData = await bService.getPropertyById(propertyId);
        console.log(propertyData);
        const propertyPhotoList = [];
        async function getPhotoData(item, index){
            const id = item.split('#')[1];
            const response = await fetch(`http://localhost:4000/api/propertyphoto/${id}`);
            const body = await response.json();
            console.log(body);
            propertyPhotoList.push(body);
        }
        await Promise.all(propertyData.PropertyPhotos.map(getPhotoData));
        console.log(propertyPhotoList);
        return res.success(res, propertyData);
    } catch (err) {
        return res.error(res, err.response.status || 500, err.response.statusText || err);
    }
}

The reason your code isn't working is because you're not waiting for all the calls to getPhotoData to finish before sending the response.

SimpleJ
  • 13,812
  • 13
  • 53
  • 93
2

Because the callback is asynchronous, you need to wait for all of the mapping functions to complete before printing the new propertyPhotoList - this can be done with Promise.all. There's no need to assign to an external array, either if you just return the item you want in the new array:

const propertyPhotoList = await Promise.all(
  propertyData.PropertyPhotos.map(getPhotoData)
);

async function getPhotoData(item, index){
  const id = item.split('#')[1];
  const response = await fetch(`http://localhost:4000/api/propertyphoto/${id}`);
  const body = await response.json();
  return body;
}
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320