1

Need help related to promises. Please refer to details below, topic is more theoretical, I don't understand what flow should I use:

  1. We have async function getDataFromUri(), which returns data, which gets filtered and saved to arr of objects, lets name it res;
  2. For each campaign(object) inside of array res I want send async request which will store products images of campaign to object. As result I should have ONE array res where ALL filtered data (campaign name, campaign images) is stored;

I need smth like:

[{
  name: "Somename",
  images: ['uri', 'uri', 'uri', 'uri', 'uri', 'uri']
},{
  name: "Somename",
  images: ['uri', 'uri', 'uri', 'uri', 'uri', 'uri']
}
]
  1. Do some other actions with RES array.

This function gets campaigns:

function getDataFromUri(uri) {
  return new Promise((resolve, reject) => {
    request.get(uri, (err, res, body) => {
     if(err || res.statusCode !== 200 ) {
       reject(handleErr(err));
     } else {
       resolve(body);
     }
   });
 });
}

This function gets images of campaign:

function getProductsOfCampaign(id) {
  var productsImagesLinks = [];
  return new Promise((resolve, reject) => {
    getDataFromUri(`SOME_URI/${id}.json`)
      .then((json) => {
        var productsList = JSON.parse(json).products;
        resolve (productsList.map((product) => product.imgSrc));
      }).catch((e) => {
        throw new Error(e);
      })
  });
}

Here I met problem:

getDataFromUri(someLink) //Get campaings;
  .then((result) => {

    //NOT WORKING FOREACH

    result.forEach((item, i) => {
      item.images = getProductsOfCampaign(item.id);
    })

    return result;
    })
  .then((result) => {
    //Do something else to array with images;
  });
  1. How can I force next after forEach .then() expression to wait all images URLs to be saved?
  2. I tried Promise.all(), but seems have lack of knowledge on how to implement it correct way.

I will really appreciate if you help me resolve this case. Thank you.

Deco Yo
  • 81
  • 5
  • Not sure if it's a duplicate, but [my answer here](https://stackoverflow.com/a/43766002/157247) shows how to do this, whether you want to do it in series or in parallel. – T.J. Crowder Oct 09 '17 at 16:13
  • You could try to use [Promise.all()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all), maybe changing some of your logic. – eventHandler Oct 09 '17 at 16:19
  • That's because `.forEach` has no return value, you need to use `.map` which can return something (the promise) to wait for. – Benjamin Gruenbaum Oct 09 '17 at 18:03
  • Avoid the [`Promise` constructor antipattern](https://stackoverflow.com/q/23803743/1048572?What-is-the-promise-construction-antipattern-and-how-to-avoid-it)! – Bergi Oct 09 '17 at 18:07

2 Answers2

3

Observe that:

  1. item in forEach is a copy.
  2. getProductsOfCampaign returns a Promise.
  3. The web is a best-effort service.

Do this:

getDataFromUri(someLink) // Get campaigns
  .then(result => {

    var promises = result.map(item =>
      getProductsOfCampaign(item.id)
        .then(products => {
          item.images = products;
          return item;
        })
        // 3: Best-effort service
        .catch(() => {})
    );

    return Promise.all(promises);
  }).then(items => {

    console.log(items);
    // Do something else to array of items with images
  });

Other readers can test for correctness with this:

function getDataFromUri(someLink) {
  return new Promise((resolve) => {
    setTimeout(resolve, 1000, [{id: 1}, {id: 2}]);
  })
}

function getProductsOfCampaign(id) {
  return new Promise((resolve) => {
    setTimeout(resolve, 1000, id * id);
  })
}

var someLink = '';

Thanks to Benjamin Gruenbaum for suggesting that .catch(() => {}) can be used with Promise.all for a best-effort service.

aaron
  • 39,695
  • 6
  • 46
  • 102
0
let campaigns = null;
getDataFromUri(someLink) //Get campaings;
 .then((result) => {
  campaigns = result;

  let pImages = []
  result.forEach((item, i) => {
    pImages.push(getProductsOfCampaign(item.id));
  });

  return Promise.all(pImages);
 })
 .then((images) => {
   campaigns.forEach((campaign, index) => {
     campaign.images = images[index];
   });

   // ... Do something else to array with images;
 });
slinhart
  • 562
  • 5
  • 17