Run Promises in a loop

Having imported ionic-native’s ‘contacts’ library, I am trying to run the Contacts.find() method, which returns a Promise, in a loop. It works when I’m hard-coding:

modifyContacts(){
    let fieldTypes: ContactFieldType[] = ["name", "displayName"];
    let options:ContactFindOptions = new ContactFindOptions();
    options.multiple = false;
    options.filter = "Max Planck";
    this.findContact(fieldTypes, options).then((data) => { 
      console.log("data 1: " + JSON.stringify(data));
      options.filter = "Ernest Rutherford";
      this.findContact(fieldTypes, options).then((data) => {
        console.log("data 2: " + JSON.stringify(data));
        options.filter = "Neils Bohr";
        this.findContact(fieldTypes, options).then((data) =>{
          console.log("data 3: " + JSON.stringify(data));
          console.log("finished all promise calls");
        })
      })
    })
    .catch(err => {
      console.log("Caught error in promise loop: " + err);
    })
}

findContact(fieldTypes: ContactFieldType[], options: ContactFindOptions): Promise<any> {
  return this.contacts.find(fieldTypes, options).then(ret_contacts => {
    return ret_contacts;
  });
}

But I need to do this dynamically - using an array of names whose length is not known in advance. I tried many methods, such as the following:

modifyContacts(){    
    let fieldTypes: ContactFieldType[] = ["name", "displayName"];
    let options:ContactFindOptions = new ContactFindOptions();
    options.multiple = false;
    let promise_arr = [];
    let currName = "";

    for(let i=0; i<this.matchedContacts.length-1; i++){
      currName = this.matchedContacts[i]['fname'] + " " + this.matchedContacts[i]['lname'];
      options.filter=currName;
      promise_arr.push(this.findContact(fieldTypes, options));
    }

    Promise.all(promise_arr).then(results => {
      console.log("ran all promises");
      console.log(JSON.stringify(results));
    });
}

But without any success. Can anyone please tell me what I’m doing wrong?

that have to return a promise and it looks not like it is doing.

There was a typo in that line - I edited it.
The method findContact() should return a Promise. Or do I call it the wrong way?

hmm, now it looks ok for me.

findContact(fieldTypes: ContactFieldType[], options: ContactFindOptions): Promise<any> {
  return this.contacts.find(fieldTypes, options).then(ret_contacts => {
   debugger;
    return ret_contacts;
  });

do you come back on the debugger?

In any sane language, what you have written should work. Unfortunately for all of us, JavaScript is not a sane language.

The problem, I believe, lies in the way you are (re-)using options. It does not get a separate value for each iteration of your loop. Try instead moving its let declaration inside the loop and see if anything changes.

Looks like the language is just about as sane as the one who is using it :slight_smile:
Unfortunately didn’t help - It looks like it doesn’t get back from the Promise.all().
Using the debugger at that spot was confusing at best…

modifyContacts() {
  // stuff
  options.multiple = false;
  let promise_arr = buildPromiseArray(options);
  Promise.all(promise_arr)
  // more stuff
}
buildPromiseArray(options: ContactFindOptions) {
  // for loop
  let findOptions = Object.assign({}, options, {filter: currName});
  // push the new promise with findOptions as the options field
}

Thanks for your interesting solution, Aaron. Using your logic, I changed the code to the following:

modifyContacts() {
    var options: ContactFindOptions = new ContactFindOptions();
    options.multiple = false;
    let promise_arr = this.buildPromiseArray(options);
    Promise.all(promise_arr).then(results => {
        console.log("ran all promises");
        console.log(JSON.stringify(results));
   });
}

buildPromiseArray(options: ContactFindOptions) : Array<any>{
    var prom_arr = [];
    // for loop
    Object.keys(this.matchedContacts).forEach(key => {
      var fieldTypes: ContactFieldType[] = ["name", "displayName"];
      var currName = this.matchedContacts[key]['fname'] + " " + this.matchedContacts[key]['lname'];
      let findOptions = Object.assign({}, options, {filter: currName});
      // push the new promise with findOptions as the options field
      prom_arr.push(this.findContact(fieldTypes, findOptions));
    });
    return prom_arr;
}

findContact(fieldTypes: ContactFieldType[], options: ContactFindOptions): Promise<any> {
    return this.contacts.find(fieldTypes, options).then(ret_contacts => {
      return ret_contacts;
    }) 
}

Though, I’m still getting no response from within the Promise.Resolve(). Maybe I’m missing something…

Are you sure all the Promises resolve?

Edit: when run independently I mean. Maybe the syntax of your code is fine and you’re asking for nonexistent information.

You’re right on! I just hard-coded the calls, and one of the findContact() returns was empty! If I’m not mistaken, if one promise fails, Promise.all() will fail as well…

I can check if ret_contacts is empty, but I assume the promise still returns. Is there a way of catching an empty promise before returning it?

In the Promise you push into the array you could add something like
.catch(err => Promise.resolve(usefulInformation));
Then every Promise resolves, and you can check later which Promises resolved by reading data, and which resolved after failing to read data.

By the way, I’m not sure about the syntax there, might need return Promise – I’ve only tried to catch errors at the end of Promise.all, not inside it before.

Thanks for putting me on the right track Aaron, It’s very much appreciated.
I will get back to this tomorrow (with a fresh head) and update this post.

… Turned out a false alarm, does not work even with correct data.

I’m starting to think there’s maybe some incompatibility between the device contacts functionality and the Promise.all().

Replacing the findContact() body with a simple Promise such as the following works fine.

findContact(){
  return new Promise((resolve, reject) => {
      setTimeout(resolve, 100, 'foo');
  }); 
}

Any suggestions welcome!

I haven’t looked at that plugin, but I looked at another recently and I thought it wasn’t Promise/A+ compliant. But I don’t know whether that affects Promise.all. I was wondering this in the back of my head though, when I read your thread.

You might want to manually create a Promise.all that by testing you verify works with the plugin.

One way to protect against this is to wrap all dodgy "Promise"s inside Promise.resolve(thingReturningDodgyPromise()). In the past, I have had to do this with WebCrypto.

1 Like

How can this be done?

Given the comment of @rapropos, I think the first thing to try is to wrap each “Promise” from the plugin in a real Promise, make an array of those, and then try Promise.all on that array. That’s cleaner than the idea I had.

I did try that as well, without success. Unless I’m doing it wrong…

findContact(fieldTypes: ContactFieldType[], options: ContactFindOptions) {
    return Promise.resolve(this.contacts.find(fieldTypes, options).then((ret_contacts) => {
      return ret_contacts[0];
    })
      .catch(error => {
        return "failed to get contacts for ...";
    }));
  }

I remain unconvinced that this all isn’t still caused by aliased objects. Can you please remove all possible traces of shared ContactFindOptions and actually instantiate a completely independent one for each interaction with the plugin? No Object.assign chicanery, please. Do not even pass that options parameter.

I do appreciate your comments. After all, it does work when using a waterfall of 'then()'s as in the first example in this post.

I did simplify the code as much as possible into the one body:

ModifyContacts() {
    var prom_arr = [];
    for (var i = 0; i < this.test_MatchContact_arr.length; i++) {
      let currName: string = this.test_MatchContact_arr[i]['fname'] + " " + this.test_MatchContact_arr[i]['lname'];
      let options: ContactFindOptions = new ContactFindOptions();
      let fieldTypes: ContactFieldType[] = ["name", "displayName"];
      console.log(currName);
      options.filter = currName;
      let promise = this.contacts.find(fieldTypes, options).then((ret_contacts) => {
        return ret_contacts[0];
      }).catch(error => {
        console.log(" -> Error assigning promise");
        return JSON.stringify(error);
      });
      prom_arr.push(promise);
    };

    Promise.all(prom_arr).then(results => {
        console.log(" -> ran all promises");
        this.contactinfo = JSON.stringify(results);
        console.log(JSON.stringify(results));
      }, reason => {
        console.log(reason);
      });
  }

But alas, it still does not get inside the Promise.all() … Being new to Promises, I wonder if I’m using them properly.