1

I am trying to build associations between a question and answers. And I am using to use Bluebird's API .map to make sure a redirect only happens after all the question.addAnswers(answer) promises are done. Therefore, in my terminal, I should see something like this:

done adding a answer to the question
done adding a answer to the question
finished

However, what I see is:

finished
done adding a answer to the question
done adding a answer to the question

Therefore, I assume the Promise.map is not working at all. Did I miss something? How can I make it work?

Here is my code:

router.post('/create', function(req, res) {
  models.Question.create({
    content: req.body.question
  })
  .then(function(question) {
    if (!question) {
      res.render('questions/new', {
          error: "Question \"#{req.body.question}\" fails to be created"
        });
    } else {
      // Update the new question to each user
      models.User.findAll()
      .then(function(users) {
        users.forEach(function(user) {
          user.addQuestion(question)
        });
      });
      Promise.map(req.body.answers, function(answer){
        return createAndAddToQuestion(question, answer, res)
      })
      .then(function(){
        console.log('finished')
        res.redirect("/questions/success/?question_id=" + question.id);
      });
    };
  })
})

var createAndAddToQuestion = function(question, answer, res) {
  models.Answer.create({
    content: answer
  })
  .then(function(ans) {
    if (ans) {
      var promise = question.addAnswer(ans)
      promise.then(function(){
        console.log("done adding a answer to the question")
      });
      return question.addAnswer(ans);
    } else {
      res.render('questions/new', {
        error: "Answer \"#{answer}\" fails to be created"
      });
    };
  });
}

UPDATE I just update the createAndAddToQuestion, so it will return a promise instead. Outcome stays the same. Promise.map is not working.

var createAndAddToQuestion = function(question, answer, res) {
  models.Answer.create({
    content: answer
  })
  .then(function(ans) {
    if (ans) {
      return question.addAnswer(ans).then(function() {
        console.log('done')
      })
    } else {
      res.render('questions/new', {
        error: "Answer \"#{answer}\" fails to be created"
      });
    };
  });
}
WeiRoR
  • 25
  • 2
  • 7
  • `createAndAddToQuestions` doesn't return anything, but presumably you want it to return a Promise of some sort. Maybe you just want `return models.Answer.create...`? – user94559 Jul 31 '16 at 09:18
  • Also, you probably want `return promise` instead of `return question.addAnswer(ans)`... otherwise you're calling that function twice. – user94559 Jul 31 '16 at 09:19
  • Thanks! That was a typo. I changed it back, but same outcome. @smarx – WeiRoR Jul 31 '16 at 18:49
  • 1
    `createAndAddToQuestion` still doesn't return anything. As I said in my first comment, maybe you want `return models.Answer.create...`. – user94559 Jul 31 '16 at 18:56

1 Answers1

0

Your most prominent problem is that createAndAddToQuestion does not return a promise, so map cannot know what to wait for.

Also you don't wait for models.User.findAll, call question.addAnswer(ans); twice, potentially try to render an error message multiple times if answers fail to be created, and do not have a generic error handler. You should be doing

router.post('/create', function(req, res) {
  createQuestion(req.body).then(function(question) {
    console.log('finished')
    res.redirect("/questions/success/?question_id=" + question.id);
  }, function(err) {
    res.render('questions/new', {
      error: err.message
    });
  }).catch(function(err) {
    console.error(err);
    res.status(500);
  });
});

function createQuestion(opts) {  
  return models.Question.create({
    content: opts.question
  })
  .then(function(question) {
    if (!question) {
      throw new Error("Question \"#{opts.question}\" fails to be created");
    }
    // Update the new question to each user
    return Promise.all([
      models.User.findAll()
      .then(function(users) {
        users.forEach(function(user) {
          user.addQuestion(question)
        })
      }),
      Promise.map(opts.answers, function(answer){
        return createAndAddToQuestion(question, answer)
      })
    ]).return(question);
  });
}

function createAndAddToQuestion(question, answer) {
  return models.Answer.create({
    content: answer
  })
  .then(function(ans) {
    if (!ans) {
      throw new Error("Answer \"#{answer}\" fails to be created");
    }
    return question.addAnswer(ans);
  })
  .then(function(){
    console.log("done adding a answer to the question")
  });
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • I tried it, an error is return: `Unhandled rejection SequelizeDatabaseError: ER_BAD_FIELD_ERROR: Unknown column 'NaN' in 'where clause' ` Also, I didn't wait for `user.findAll()` intentionally because I don't need the result from that method when I redirect. Do I have to wait for it to finish although I don't need the result right away? This way, it can save some time. In that case, my original code should works, right? In addition, in your code, how does the question get pass to the route from `createQuestion`?`question.addAnswer` will return question in its promise? – WeiRoR Jul 31 '16 at 18:40
  • @WeiRoR: Thanks for the hints, updated my answer. I had indeed forgotten to return the `question` from the helper function. Where that database error comes from I don't know, but that it is an unhandled rejection does worry me. Regarding the findAll, I would still wait for it so that you can catch errors from its execution. You can carry it out in parallel to the other actions though. – Bergi Jul 31 '16 at 20:47
  • That works. Thank you so much! Could you explain a little bit how the question got returned through `Promise.all([...]).return(question)`? Also, inside the route, where is `function(err) {...}` in `.then(...)` from? I thought there is usually only one function inside of `.then(...)`. – WeiRoR Jul 31 '16 at 23:52
  • @WeiRoR `Promise.all` just waits for the question to be added to the users, and the answers to be added to the question. Then, [`return`](http://bluebirdjs.com/docs/api/return.html) does what its name says. Regarding two callbacks, see [this post](http://stackoverflow.com/a/24663315/1048572). – Bergi Aug 01 '16 at 01:29
  • Thank you so much! – WeiRoR Aug 01 '16 at 05:30