Search code examples
javascriptes6-promise

Promises in a While Loop where condition of while is based on result of the promise


I need to do an indeterminate loop where the condition of the loop will be based on the response from a promise within the loop that gets some data from a database. The loop should be done from within a promise that then returns the total list of records pulled from the database. This is somewhat simplified from the real world example, but have tried to provide some basic code to demonstrate what I want essentially to do.

The call to getLinkedEntities passes in an entity and based on a linked entity id begins the loop where linked entities are returned and then returned from the original promise.

The problem I have is that because of the asynchronous nature of promises the moment getElement is called within the while loop it never resolves because the function continues and gets stuck in continuous loop.

Hopefully this code describes well enough what I am trying to do, i.e. end up with an array of linked entities, but how can it be done in a synchronous way that allows all promises to return?

   function getLinkedEntities(startEntity) { 
        return new Promise(function (resolve, reject) {

            var linkedEntities = [];
            var entityId = startEntity.id;
            var someOtherId = startEntity.linkedentityid;

            var stopLoop = false;

            do {

                getElement(someOtherId).then(function (entity) {
                    linkedEntities.push(entity)
                    entityId = entity.id;
                    someOtherId = entity.linkedentityid;
                    stopLoop = entity.linkedentityid === null ? 1 : 0;
                });

            }
            while (!stopLoop);

            resolve(linkedEntities);

        });
    }

    function getElement(id) {
        return new Promise(function (resolve, reject) {
            // go to database and get an entity
            resolve(entity);
        });
    }

    getLinkedEntities(someEntity).then(function (response) {
        // do somethign with linkedEntities
    });

Solution

  • Without using async / await, what you can do is create an inner function, that calls itself.

    eg.

      ...
      function getNext() {
         return getElement(someOtherId).then(function (entity) {
           linkedEntities.push(entity)
           entityId = entity.id;
           someOtherId = entity.linkedentityid;
           stopLoop = entity.linkedentityid === null ? 1 : 0;
           if (stopLoop) return resolve(linkedEntities);
           else return getNext();
         });
      }
      //boot strap the loop
      return getNext();
    }
    

    Basically this just keeps calling getNext until stopLoop is set, and then resolves linkedEntities, if not it calls getNext again. IOW: exactly what your while loop is doing.

    Below is a working snippet that uses this idea, instead of getElement, I've replaced with a sleep promise. Basically fill's an array waiting 500ms between each array push.

    function sleep(ms) {
      return new Promise(function (resolve) {
        setTimeout(resolve, ms);
      });
    }
    
    function test() {
      var ret = [];
      function getNext() {
        return sleep(500).then(function () {
          if (ret.length >= 10) {
            return ret;
          } else {
            console.log('tick: ' + ret.length);
            ret.push(ret.length + 1);
            return getNext();
          }
        });
      }
      return getNext();
    }
    
    test().then(function (r) {
      console.log('Resolved with ->');
      console.log(r.join(','));
    });