0

I'm trying to call a promise function recursively.

The following call service.getSentenceFragment() returns upto 5 letters from a sentence i.e. 'hello' from 'helloworld. Providing a nextToken value as a parameter to the call returns the next 5 letters in the sequence. i.e. 'world'. The following code returns 'hellohelloworldworld' and does not log to the console.

var sentence = '';
getSentence().then(function (data)) {
    console.log(sentence);
});

function getSentence(nextToken) {
    return new Promise((resolve, reject) => {
        getSentenceFragment(nextToken).then(function(data) {
            sentence += data.fragment;
            if (data.nextToken != null && data.nextToken != 'undefined') {
                getSentence(data.NextToken);
            } else {
                resolve();
            }
        }).catch(function (reason) {
            reject(reason);
        });
    });
}

function getSentenceFragment(nextToken) {
    return new Promise((resolve, reject) => {
        service.getSentenceFragment({ NextToken: nextToken }, function (error, data) {
            if (data) {
                if (data.length !== 0) {
                    resolve(data);
                }
            } else {
                reject(error);
            }
        });
    });
}
cyorkston
  • 225
  • 1
  • 10
  • 1
    Oh no. The promise constructor antipattern. https://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it – Jonas Wilms Mar 20 '18 at 14:49
  • It sounds like you're going to want to look into using the `yield` keyword, with a `generator`. This article should be helpful: https://davidwalsh.name/async-generators – jmcgriz Mar 20 '18 at 14:54
  • 1
    Also, to just fix what you've got, I'm pretty sure you just need to replace `getSentence(data.NextToken);` with `resolve(getSentence(data.NextToken));` – jmcgriz Mar 20 '18 at 15:02

2 Answers2

2

Cause when you do this:

getSentence(data.NextToken);

A new Promise chain is started, and thecurrent chain stays pending forever. So may do:

getSentence(data.NextToken).then(resolve, reject)

... but actually you could beautify the whole thing to:

async function getSentence(){
  let sentence = "", token;

  do {
   const partial = await getSentenceFragment(token);
   sentence += partial.fragment;
   token = partial.NextToken;
  } while(token)

  return sentence;
}

And watch out for this trap in getSentenceFragment - if data is truthy but data.length is 0, your code reaches a dead end and the Promise will timeout

// from your original getSentenceFragment...
if (data) {
  if (data.length !== 0) {
    resolve(data);
  }
  /* implicit else: dead end */
  // else { return undefined }
} else {
  reject(error);
}

Instead, combine the two if statements using &&, now our Promise will always resolve or reject

// all fixed!
if (data && data.length > 0)
  resolve(data);
else
  reject(error);
Mulan
  • 129,518
  • 31
  • 228
  • 259
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151
1

You could recursively call a promise like so:

getSentence("what is your first token?")
.then(function (data) {
  console.log(data);
});

function getSentence(nextToken) {
  const recur = (nextToken,total) => //no return because there is no {} block so auto returns
    getSentenceFragment(nextToken)
    .then(
      data => {
        if (data.nextToken != null && data.nextToken != 'undefined') {
          return recur(data.NextToken,total + data.fragment);
        } else {
          return total + data.fragment;
        }
    });//no catch, just let it go to the caller
  return recur(nextToken,"");
}

function getSentenceFragment(nextToken) {
  return new Promise((resolve, reject) => {
    service.getSentenceFragment({ NextToken: nextToken }, function (error, data) {
      if (data) {
        if (data.length !== 0) {
          resolve(data);
        }
      } else {
        reject(error);
      }
    });
  });
}
HMR
  • 37,593
  • 24
  • 91
  • 160