0

How can I repeat a .then clause as a loop? I need to wait for the .then clause promise to resolve, and then if the exit criteria has not been met, I need to repeat the same operation again ... until the exit criteria is met.

My use case is a repeating ajax GET call for multiple "pages" of data until all the data has been retrieved.

I can simplify and simulate the issue as follows. This is the .then clause that I would love to .thenRepeat:

  .then(function(retData) {
      namesList.push.apply(namesList, retData); // accum data into list
      queryParms.pageNo++;                      // set to next page
      return pretendAjaxFnc(queryParms);        // get next page
  })

And this is a runnable example:

function pretendAjaxFnc(obj) {
  return Promise.resolve().then(function() {
    if (obj.pageNo < 6) { // create a pretend "I'm done" point.
      // return a couple dummy records
      return [{  fld1: "data1",  fld2: "data2" }, 
              {  fld1: "data1",  fld2: "data2" }];
    } else {
      // this is the exit criteria
      // It will actually be 404 http status converted to a custom exception
      throw new Error("NO-MORE-DATA");
    }
  });
};

function clientAccumulator() {
  var namesList = [];
  var queryParms = {
    pageNo: 1
  };

  return pretendAjaxFnc(queryParms)
    .then(function(retData) {
      namesList.push.apply(namesList, retData); // append data to list
      queryParms.pageNo++; // set to get next page
      console.log("EIC.GTEST11 list.len: ", namesList.length);
      return pretendAjaxFnc(queryParms);
    })
    // repeat until some exit criteria - like an exception
    .then(function(retData) {
      namesList.push.apply(namesList, retData);
      queryParms.pageNo++;
      console.log("EIC.GTEST21 list.len: ", namesList.length);
      return pretendAjaxFnc(queryParms);
    })
    // repeat until some exit criteria - like an exception
    .then(function(retData) {
      namesList.push.apply(namesList, retData);
      queryParms.pageNo++;
      console.log("EIC.GTEST31 list.len: ", namesList.length);
      return pretendAjaxFnc(queryParms);
    })
    // repeat until some exit criteria - like an exception
    // ...
    .catch(function(ex) {
      if (ex.message === "NO-MORE-DATA") {
        return namesList;
      } else {
        throw ex;
      }
    });
};

clientAccumulator(); // Run the function

This is Browser code that needs to run on current iOS/Safari & Firefox (though preferably more variations). I'm using AngularJS, but I believe I've removed any of that specificity.

Does anyone have a .thenRepeat they can point me to?

GaryL
  • 1,385
  • 8
  • 11

2 Answers2

2

Just put the then chain into afunction, so that it is recursive:

  function recurse() {
    return pretendAjaxFnc(queryParams)
      .then(function(retData) {
         namesList.push(...retData);
         queryParms.pageNo++;
         return recurse();
    });
 }

Working example:

function pretendAjaxFnc(obj) {
  return Promise.resolve().then(function() {
    if (obj.pageNo < 6) { // create a pretend "I'm done" point.
      // return a couple dummy records
      return [{  fld1: "data1", fld2: "data2" }, 
              {  fld1: "data1", fld2: "data2" }];
    } else {
      // this is the exit criteria
      // It will actually be 404 http status converted to a custom exception
      throw new Error("NO-MORE-DATA");
    }
  });
};

function clientAccumulator2() {
  var namesList = [];
  var queryParms = {
    pageNo: 1
  };
  console.log("TEST00 list.len: ", namesList.length);

  function recurse() {
    return pretendAjaxFnc(queryParms)
      .then(function(retData) {
        namesList.push(...retData);
        console.log("TEST01 list.len: ", namesList.length);
        queryParms.pageNo++;
        return recurse();
      });
  }

  return recurse()
    // repeat until some exit criteria - like an exception
    .catch(function(ex) {
      if (ex.message === "NO-MORE-DATA") {
        return namesList;
      } else {
        throw ex;
      }
    });
};
clientAccumulator2(); // Run the function
GaryL
  • 1,385
  • 8
  • 11
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
1

Recursion is your friend.

After re-reading again, it seems that this might be more what you're looking for:

repeatPromiseUntil({
    promiseMethod,
    doneConditionMethod,
    waitInterval = 100
}){
    return promiseMethod()
        .then((result) => {
            if (doneConditionMethod(result)) {
                return this.$q.resolve(result);
            }

            return this.$timeout(waitInterval)
                .then(() => this.repeatPromiseUntil({promiseMethod, doneConditionMethod, waitInterval}));
        });
}

Here's an example from my own code (It's rewritten from Typescript for this answer, so there might be a typo or two)

function retryPromise({
    maxRetries,
    promiseMethod,
    successConditionMethod,
    retryInterval,
    increaseInterval = true
}) {
    if (increaseInterval) {
        retryInterval = retryInterval + 1000;
    }

    return promiseMethod().then((result) => {
        if (successConditionMethod(result)) {
            return $q.resolve(result);
        } else if (maxRetries > 0) {
            return $timeout(retryInterval)
                .then(() => {
                    return retryPromise({
                        maxRetries: maxRetries - 1,
                        promiseMethod,
                        successConditionMethod,
                        retryInterval,
                        increaseInterval
                    });
                });
        } else {
            retryInterval = 0;
            return $q.reject();
        }
    });
}

Edit: I've added some angular-specifics that I've used myself (missed the comment about angular initially)

Thor Jacobsen
  • 8,621
  • 2
  • 27
  • 26