1

The code below cycles through a list of tags (strings) and gets info and then images from services. Then it pushes a newly created and assembled ProductItem object to an array of products that now are populated with images.

var toReturn = [];
for (var i in tags){
     var productItem: ProductItem= new ProductItem();

      self.productService.getInfo(tags[i])
                .subscribe(function(info){
                       productItem.info = info;

                       self.productService.getImages(tags[i])
                            .subscribe(function(imageUrls){
                                  productItem.images = imageUrls;
                                  toReturn.push(productItem);
                      });
                });


}

However, there is a problem here.

The problem is that I end up with a toReturn array filled with identical products - all of them corresponding to the last tag in the tags array.

I realized that this must have to do with the scoping for ProductItem. I think that since the last element in tags is creating the last productItem, all of the unfulfilled promises/observables are fulfilling on this final productItem and not their own productItem which I expected them to retain.

Does anyone have any ideas on how to get around this problem?

CodyBugstein
  • 21,984
  • 61
  • 207
  • 363
  • http://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example – zakki Mar 31 '16 at 07:49

1 Answers1

-1

The problem here is that you mix a synchronous loop (your for loop) with asynchronous processing. You finish to iterate before all requests end.

To fix your problem, you need to wait for an element to be handled before iterating to the next one.

Update

This way your code would be refactored to the following:

var toReturn = [];
Observable.from(tags).flatMap((tag) => {
  // STEP1 - load the info for a tag and instantiate
  // a new product item

  var productItem: ProductItem = new ProductItem();

  // I use forkJoin since we need 'tag', productItem and the
  // return of getInfo in the next flatMap
  return Observable.forkJoin([
    Observable.of(tag),
    Observable.of(productItem),
    self.productService.getInfo(tag)
  ]);
}).flatMap((result) => {
  // STEP2 - load the images for a tag and set
  // info on the product item

  var tag = result[0];
  var productItem = result[1];
  var info = result[2];

  productItem.info = info;

  // I use forkJoin since we need productItem and the
  // return of getImages in the next map
  Observable.forkJoin([
    Observable.of(productItem),
    this.productService.getImages(tag)
  ]);
}).map((result) => {
  // STEP3 - set the images on the product item

  var productItem = result[0];
  var images = result[1];

  productItem.images = imageUrls;
  return productItem;
}).do((productItem) => {
  // STEP4 - add the product item in the array

  toReturn.push(productItem);
}).subscribe(
  () => { },
  () => { },
  () => {
    console.log(`completion. The toReturn variable is
      completely filled.`
  }
);
Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • Thamks! Do I need to import something in order to creat e a Promise? – CodyBugstein Mar 31 '16 at 08:03
  • You're welcome! Promises are part of ES6. So either it's part of the browser or you need to include the corresponding shim (es6-promise)... – Thierry Templier Mar 31 '16 at 08:06
  • I'm working in Angular 2 with Typescript. I'll try it out thanks! – CodyBugstein Mar 31 '16 at 08:07
  • I find it very difficult to understand your code :( – CodyBugstein Mar 31 '16 at 19:34
  • In fact there are two parts. The first one: the Rx one. You should leverage observable operators (flatMap, map, do) to create the asynchronous processing chain with the loop. The second one: to wait for the end of the asynchronous processing to integrate to the next element in the loop. A bit work needs to ne done at this level. In order to reduce the complexity, we could split the processing into two methods: one for each part. – Thierry Templier Mar 31 '16 at 20:36