admin管理员组

文章数量:1391925

Apologies for the vague title - I can't think of a way to sum this problem up succinctly.

Here's the deal. I have a Node controller that has to do some sequential database operations, going something like this:

0. Given a surveyId
1. Find all entries in the question table, where question.surveyId = surveyId.
2. For each row in the question table returned by the previous query:
    a) Find all entries in the `answer` table, where answer.questionId = question.id
    b) For each row in the answer table returned by the previous query:
       (i) Find the count of all entries in the vote table where vote.answerId = answer.id

The controller needs to return an object containing the count of the number of times there is an entry in the vote table for each answerId. It would look something like {1:9, 2:21, 3:0}, where 1, 2, 3 are answer IDs and 9, 21, and 0 are counts of the number of rows with that answer ID in the vote table.

I've been using the Q library to avoid really deeply nested callbacks. I have a runQuery utility method that returns a promise and resolves it when the database IO is finished.

Right now, I have something that looks like:

runQuery("find questions with surveyid matching given surveyid")
.then({
    "for each question row returned by query:"
         runQuery("find answers with questionid matching this question row")
         .then({
             "for each answer row returned by query:"
             runQuery("find votes with answerID matching this answer row")
             .then({
                 "for each vote row"
                      "increment the count for this answerid in the return object"    
             })
         })
})

The problem is, the return object is always empty when the controller returns, because there hasn't been enough time for all the database ops to finish and the promises to resolve (at least, I think that's the issue - it's obviously really difficult to troubleshoot things like these asynchronous promises.)

Is there a better way of doing this? Should I just give up on Q and deal with a bunch of nested callbacks?

Apologies for the vague title - I can't think of a way to sum this problem up succinctly.

Here's the deal. I have a Node controller that has to do some sequential database operations, going something like this:

0. Given a surveyId
1. Find all entries in the question table, where question.surveyId = surveyId.
2. For each row in the question table returned by the previous query:
    a) Find all entries in the `answer` table, where answer.questionId = question.id
    b) For each row in the answer table returned by the previous query:
       (i) Find the count of all entries in the vote table where vote.answerId = answer.id

The controller needs to return an object containing the count of the number of times there is an entry in the vote table for each answerId. It would look something like {1:9, 2:21, 3:0}, where 1, 2, 3 are answer IDs and 9, 21, and 0 are counts of the number of rows with that answer ID in the vote table.

I've been using the Q library to avoid really deeply nested callbacks. I have a runQuery utility method that returns a promise and resolves it when the database IO is finished.

Right now, I have something that looks like:

runQuery("find questions with surveyid matching given surveyid")
.then({
    "for each question row returned by query:"
         runQuery("find answers with questionid matching this question row")
         .then({
             "for each answer row returned by query:"
             runQuery("find votes with answerID matching this answer row")
             .then({
                 "for each vote row"
                      "increment the count for this answerid in the return object"    
             })
         })
})

The problem is, the return object is always empty when the controller returns, because there hasn't been enough time for all the database ops to finish and the promises to resolve (at least, I think that's the issue - it's obviously really difficult to troubleshoot things like these asynchronous promises.)

Is there a better way of doing this? Should I just give up on Q and deal with a bunch of nested callbacks?

Share Improve this question asked Oct 17, 2013 at 21:15 Isaac Dontje LindellIsaac Dontje Lindell 3,4567 gold badges25 silver badges38 bronze badges 5
  • shouldn't you be returning the runQuery? – Davin Tryon Commented Oct 17, 2013 at 21:18
  • 2 If you are using a database that supports SQL then your entire operation should be acplished using just one SQL query. If not then you need to re-write the code that needs this result to fire off asynchronously after the whole operation has finished. Asynchronicity is contagious - if one piece of an operation is asynchronous, then ALL of the operation must be asynchronous. – Mike Edwards Commented Oct 17, 2013 at 21:21
  • @MikeEdwards I like that idea - that hadn't even occurred to me. I'm much more familiar with Javascript than SQL! Thanks. – Isaac Dontje Lindell Commented Oct 17, 2013 at 21:36
  • What is that thing that you're passing into the then method? It should be a callback function, but yours looks like an object or a block with a string ment. – Bergi Commented Oct 17, 2013 at 22:58
  • @Bergi - that's just my quick n' dirty pseudocode :) – Isaac Dontje Lindell Commented Oct 18, 2013 at 1:08
Add a ment  | 

2 Answers 2

Reset to default 5

There are merits to doing this with a single query. There would be no “chat” between the client and server, which will save you about 2 round trips across the network in this case (typically a tenth of a second each, about the time it takes to blink, which is to say, a perceptible amount of human time and a long boring wait for a machine).

However, should you ever need to pose promises sequentially with Q, there are many options. This is one that matches the form of your pseudocode. It would be good to break it into smaller functions.

runQuery("find questions with surveyId matching given surveyId")
.then(function (questions) {
    return Q.all(questions.map(function (question) {
        return runQuery("find answers matching question.id")
        .then(function (answers) {
            question.answers = answers;
            return Q.all(answers.map(function (answer) {
                return runQuery("find votes for each matching question")
                .then(function (votes) {
                    answer.votes = votes;
                    return answer;
                })
            }))
        })
        .thenResolve(question);
    }))
});

This would produce a promise for an array of questions, annotated with its respective array of answers, and the answers annotated with their votes.

See also https://github./kriskowal/q#sequences

You can't use regular sequential programming with asynchronous operations. Thus, you can't have a return object that the next line of sequential code can use.

Instead, the code that uses the return object must be called from the success handler or promise pletion of the last operation. This is how asynchronous coding works and one of these types of techniques must be adopted for things to work properly.

Thus, in your pseudo-code, it would look like this (the line starting with ** is what I added):

runQuery("find questions with surveyid matching given surveyid")
.then({
    "for each question row returned by query:"
         runQuery("find answers with questionid matching this question row")
         .then({
             "for each answer row returned by query:"
             runQuery("find votes with answerID matching this answer row")
             .then({
                 "for each vote row"
                      "increment the count for this answerid in the return object"    
                  ** call function and pass it the return object 
                     to process the final result
             })
         })
})
// you cannot put code here that uses the return object because
// the return object is not yet available

本文标签: javascriptUsing nested Q promises for sequentialdependent operationsStack Overflow