shapiro yaacov
shapiro yaacov

Reputation: 2346

How can I run the same promise again depending on the result from the previous run?

I have some code that is wrapped in a promise. This code goes and edits some records in a remote DB and returns with the number of edited records. If the number of edited records is larger than 0 (i.e., some work has been done), then I'd like to run the same method again and again until I get a 0 back (i.e., no more records need editing).

I've tried some different ways, but they all do not terminate (i.e., the resolve is not called) or they don't run properly.

First version I tried:

const doEdits = () => {
  return new Promise((resolve, reject) => {
    someDB.performEdits()
    .then(count => {
      if (count > 0) {
        doEdits()
      } else {
        resolve()
      }
    })
    .catch(reject)
  })
}

return new Promise((resolve, reject) => {
  someDB.init()
  .then(doEdits)
  .catch(reject)
})

Second version I tried:

const doEdits = () => {
  return new Promise((resolve, reject) => {
    someDB.performEdits()
    .then(count => {
      resolve(count)
    })
    .catch(reject)
  })
}

return new Promise((resolve, reject) => {
  someDB.init()
  .then(doEdits)
  .then(count => {
    if (count > 0 ) {
      return doEdits()   // 'doEdits()' did not work...
    } else {
      resolve()
    }
  })
  .catch(reject)
})

Any and all suggestions are welcome.

Upvotes: 1

Views: 2643

Answers (2)

Will
Will

Reputation: 2191

Don't Make Spaghetti

You really shouldn't need to create a bunch of promises, especially since the underlying library you are using is already returning you a promise.This approach should accomplish your goal without all the extra sauce:

const doEdits = count => {
  if(count > 0) {
    return someDB.performEdits().then(doEdits);
  }
};

return someDB.init()
  .then(() => {
    performEdits().then(doEdits)
  });

This will chain the promises from the subsequent db calls to the first promise. Any subsequent thens (and catches) will operate on the returned promise (ie, wait for it to resolve or reject), which will potentially be replaced by a new promise when resolved before being passed to them.


Why your code didn't work

Once you decide you're going to instantiate a Promise, you have taken on the responsibility of handling the resolve and reject callbacks - and committed your self to callback hell. Exactly the kind of thing Promises are supposed to solve.

In both snippets, as soon as you see count > 0 and call into doEdits, you abandon the previous Promise, instantiate a new one, and end up with a trail of unresolved promises in your wake. You would need to pass the resolve and reject functions into the new promise and make sure they get resolved or rejected as appropriate when the new promise resolves or rejects.

Don't Do This

function doEdits(previousPromise) {
  someDB.performEdits()
  .then((count) => {
    if(count > 0) {
      doEdits(this);
    } else {
      resolve();
    }
  })
};

return new Promise((resolve, reject) => {
  someDB.init()
  .then(() => {
    someDB.performEdits()
    .then((count) => {
      if(count > 0) {
        doEdits(this);
      } else {
        resolve();
      }
    })
  })
});

Because it's wasteful, and also doesn't handle the case if performEdits iteration 33 rejects. In theory, this might work, but it's a whole lot harder to reason about.

Upvotes: 1

Bergi
Bergi

Reputation: 665276

Yes, you were never calling resolve in the if branch. But you shouldn't use a resolve function anyway here, avoid the Promise constructor antipattern! Just do

function doEdits() {
  return someDB.performEdits().then(count => {
    if (count > 0) {
      return doEdits();
    } else {
      return undefined; // ???
    }
  });
}

return someDB.init().then(doEdits);

Upvotes: 3

Related Questions