2

I have the following function, my goal is to push to the items list, when the components can identify their parent item. The problem that I have is that when I am pushing to the list console.log() shows me that the object is in there, but when I return the list and catch it in another function, there is nothing in the list. I think that the items list is returned before the code above it is done.

 private get_items_for_request() {
   return this.Item.forRequest(this.request.id, ['group'])
     .then((_items) => {
       var items = [];
       for (var item of _items) {
         return this.ItemComponent.forItem(item.id, ['type'])
           .then((_components) => {
             for (var component of _components) {
               if (component.type.can_identify_item) {
                 items.push({
                   group_id: item.group.reference,
                   identifier_code: this.remove_check_digit_if_necessary(
                       component.identifier_code),
                   quantity: 1
                 });
                 break;
               }
             }
           }, (reason) => {
             this.Toast.error(
                this.gettextCatalog.getString('components.load_failed'));
             return [];
           });
       }
       return items;
     }, (reason) => {
       this.Toast.error(
           this.gettextCatalog.getString('items.failed_load'));
       return [];
     });
 }
GiftZwergrapper
  • 2,602
  • 2
  • 20
  • 40

2 Answers2

2

I'm afraid the trouble is with your approach, not with the code itself. A promise is just that - a promise of data some time in future. So, when you function returns (immediately), the promise is not resolved yet and the data your caller function captures is still empty. I don't see console.log() in your code, but would suspect you put it somewhere inside then(), which will be called once data has been received. So you will see data being logged. Trouble is by that time get_items_for_request() has already returned and your caller function has already moved on.

If you want to use a promise, you have to use a callback to be called once promise is resolved. But if you want actual data returned to the caller you need to fetch data synchronously.

For the synchronous fetch, check this response. But beware that synchronous fetch will hurt responsiveness of your script.

For an asynchronous fetch (using promises), you need to define a callback called once all data has been fetched. I won't try to fix your code, but it should go along the following sketch in javascript. Though keep in mind it's just a sketch.

function get_items_for_request(onSuccess) {
  var items = []
  var total = -1
  var sofar = 0;
  this.Item.forRequest(this.request.id, ['group'])
     .then(function (_items) {
       var items = [];
       total = _items.length // remember how many nested calls will be made
       for (var item of _items) {
         this.ItemComponent.forItem(item.id, ['type'])
           .then(function (_components) {
             // push received data to the items array here
             sofar++
             if (sofar == total) { // all done 
               onSuccess(items)
             }
          }
       }
   }
} 
Community
  • 1
  • 1
Seva
  • 2,388
  • 10
  • 9
0

In the first promise callback, you have two returns, only the first one runs. You return a promise after the first for, which you resolve to undefined. You should wait for an array of promises, each corresponding to a this.ItemComponent.forItem call.

Promise.all helps with this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

You should do something like this:

    return this.Item.forRequest(this.request.id, ['group']).then((_items) => {
        return Promise.all(_items.map(function (item) {
            return this.ItemComponent.forItem(item.id, ['type']).then((_components) => {
                for (var component of _components) {
                    if (component.type.can_identify_item) {
                        return {
                            group_id: item.group.reference,
                            identifier_code: this.remove_check_digit_if_necessary(
                                component.identifier_code),
                            quantity: 1
                        };
                    }
                }
            }, (reason) => {
                this.Toast.error(
                    this.gettextCatalog.getString('components.load_failed'));
                return [];
            });
        }));
    }, (reason) => {
        this.Toast.error(
            this.gettextCatalog.getString('items.failed_load'));
        return [];
    } )

If you only want one item, find the first non-falsy element in the resulting array

  • @GiftZwergrapper if you want to receive a working code, you have to link a plunkr example, or at least the whole file. I'm not sure what did you expect, we're not mediums. If you use Angular 1, the `$q` service is your Promise library. – Zsolt Gyöngyösi May 13 '16 at 13:20
  • 2
    @GiftZwergrapper also, please note that the error I pointed out is still there, you're being a bit aggressive with the downvotes here. – Zsolt Gyöngyösi May 13 '16 at 13:24
  • In angular you can use `$q.all` for the same purpose – Icycool May 25 '16 at 08:29
  • $q.all is better because it will trigger digest cycle. Promise.all may not. – user2954463 Nov 27 '18 at 23:11