7

I'm doing kind of QA (Question/Answers) application using the Bluebird library. So here's the scenario:

  1. User fill the form with answers for some number of questions (e.g 5 questions).
  2. A question has more than 1 possible answer: "Question has many answers"
  3. Answers are encrypted (bcrypt) on database using node.bcrypt
  4. When looping through answers, if user answer matches, there's no need to continue checking the answer for that question.

So it's a common problem to solve when doing things synchronous, but I'm a little lost to do that async with promises.

Here's a sample of what I don't know how to proceed:

  .then(function(answers) {
    var compare = Promise.promisify(bcrypt.compare);
    // foreach answer, I need to check like this
    // compare(answer.password, user.password).then(function(match){
    //      if (match) break; <-- something like this
    //   })
  })
Benjamin Gruenbaum
  • 270,886
  • 87
  • 504
  • 504
rizidoro
  • 13,073
  • 18
  • 59
  • 86
  • You may have to get the encrypted answer separately before the loop, and inside the callback start the loop and just do basic checking `(if hash == password) break;` – tymeJV Feb 13 '14 at 14:59
  • @tymeJV I think I can't do that, cause when I hash the password with bcrypt, a different value is generated from the database previously generated value... that's the reason why I need the bcrypt compare function... Maybe I'm wrong... – rizidoro Feb 13 '14 at 15:06
  • Don't have my bcrypt repo up in front of me but that sounds right - callback hell :\ - this post might help: http://stackoverflow.com/questions/13214862/async-functions-inside-foreach-loops-with-js – tymeJV Feb 13 '14 at 15:11

3 Answers3

3

Assuming you want to call the compares sequentially, this will do it:

.then(function(answers) {
    var compare = Promise.promisify(bcrypt.compare),
        i = 0;
    return Q(false).then(function checkNext(res) {
        return res ||
               i<answers.length && compare(answers[i++].password, user.password)
                                     .then(checkNext);
    });
})

It will "recursively" step trough the answers array, stopping on the first true result. To return the correct answer (instead of just true for "found") or null (if not found) like @Noseratio's code, you could use

    var i = 0, answer;
    return Q(false).then(function checkNext(res) {
        return res ? answer : (i<answers.length || null) && compare((answer = answers[i++]).password, user.password).then(checkNext);
    });

or better the more verbose

function next(i) {
    if (i < answers.length)
        return compare(answers[i].password, user.password).then(function(res) {
             return res ? answers[i] : next(i+1);
        });
    else
        return null;
}
return next(0);
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

The following solution (untested) implements a state machine to simulate foreach loop. The result promise is resolved when the match has been found, or when there is no more answers to compare:

  .then(function(answers) {
    var result = new Promise();
    var i = 0;
    function nextStep() {
      if (i >= answer.length)
        result.resolve(null);
      else {
        var answer = answers[i];
        if (compare(answer.password, user.password).then(function(match) {
          if (match)
            result.resolve(answer);
          else {
            i++;
            nextStep(); // do the next step
          }
        })
      }
    }
    process.nextTick(nextStep); // do the first step asynchronously    
    return result; // return the result as a promise
  });
noseratio
  • 59,932
  • 34
  • 208
  • 486
0

It could be a simple solution:

let breakLoop = false
for (let answer of arr) {
  if (breakLoop) continue
  compare(answer.password, user.password)
    .then(match => {
      if (match) breakLoop = true
    })
    .catch(err => breakLoop = true)
}
Arthur Ronconi
  • 2,290
  • 25
  • 25