简体   繁体   中英

Node.js - How to chain Promise in The right way

I recently moved from callback functions to promises in node.js. I want to preform async query to the DB (psql) in the most elegant way. I was wondering if the following code is the right way to do it or if I can chain for example the promises in a way of first().then(second).then(third) .

function queryAll(req, res) {
    searchQuery()
    .then((rows) => {
        console.log(rows);
        totalQuery()
        .then((total) => {
            console.log(total);
        });
    });
    res.json({"rows": rows, "total": total});
}

function searchQuery() {
    return new Promise(function(resolve, reject) {
        var rowData = { rows: {} };
        pool.query('select age, sex from workers;', values, function(err, result) {
            if(err) {
                return console.error('error running query', err);
                reject(err);
            }
            rowData.rows = result.rows;
            resolve(rowData);
        });
    });
}

function totalQuery() {
    return new Promise(function(reject, resolve) {
        var totalData = { totals: {} };
        pool.query('select sex, scores from workers group by sex;', values, function(err, result) {
            if(err) {
                return console.error('error running query', err);
                reject(err);
            }
            totalData.totals = result.rows;
            resolve(totalData);
        });
    });
}
var rowData = { rows: {} };
var totalData = { totals: {} };

First of all, these make no sense stored in variables since there's nothing else on the object. Just resolve with the rows directly instead.

return console.error('error running query', err);

Also, don't just console.log your errors. then accepts a second callback that executes on thrown errors or rejected promises. Throw this message in an error or reject with it instead. Also, I would leave logging to the consumer.

function queryAll(req, res) {
    searchQuery()
    .then((search) => {
        console.log(rows);
        totalQuery()
        .then((total) => {
            console.log(total);
        });
    });
    res.json({"rows": rows, "total": total});
}

rows and total don't exist anywhere. Plus, by the time res.json executes, rows and total (assuming they come from inside the callbacks) won't exist yet since the whole sequence is async. You'll get undefined values as results.

I see little point in running searchQuery and totalQuery in sequence as they're not dependent on each other. It's better to run them parallel instead. Use Promise.all for that.

function queryAll(req, res) {
  Promise.all([
    searchQuery(),
    totalQuery()
  ]).then(values => {
    const rows = values[0];
    const total = values[1];
    res.json({"rows": rows, "total": total});
  }, function(e){
    // something went wrong
  });
}

function searchQuery() {
  return new Promise(function(resolve, reject) {
    pool.query('select age, sex from workers;', values, function(err, result) {
      if(err) reject(err);
      else resolve(result.rows);
    });
  });
}

function totalQuery() {
  return new Promise(function(reject, resolve) {
    pool.query('select sex, scores from workers group by sex;', values, function(err, result) {
      if(err) reject(err);
      else resolve(result.rows);
    });
  });
}

You have a few issues in the code:

  • You return before executing reject()
  • There is an undefined rows variable (mismatch with search )
  • res.json is executed before the results are in.
  • The promises resolve to objects like { rows: rows } , but the main function seems to expect the plain numbers, not the objects. So let the promises just resolve to numeric values.
  • The second SQL is ambiguous since the second field is not aggregated and does not appear in the group by clause either. Assuming you want to sum the scores, use sum() .
  • The second query is only launched after the first one has returned results, but this can be done in parallel
  • You have very similar code repeated. Try to reuse code and make the SQL statement an argument.

Here is how I would suggest to do it:

function queryAll(req, res) {
    return Promise.all([searchQuery(), totalQuery()]).then(([rows, total]) => {
        console.log('rows', rows);
        console.log('total', total);
        // Make sure to only access the promised values in the `then` callback
        res.json({rows, total});
    });
}

function searchQuery() {
    return promiseQuery('select age, sex from workers;');
}

function totalQuery() {
    // When you group, make sure to aggregate:
    return promiseQuery('select sex, sum(scores) as scores from workers group by sex;');
}

function promiseQuery(sql) { // reusable for other SQL queries
    return new Promise(function(resolve, reject) {
        pool.query(sql, values, function(err, result) {
            if(err) {
                // Do not return before calling reject!
                console.error('error running query', err);
                reject(err);
                return;
            }
            // No need for a variable or object, just resolve with the number of rows
            resolve(result.rows);
        });
    });
}

The most elegant solution would be via pg-promise :

function queryAll(req, res) {
    db.task(t => {
        return t.batch([
            t.any('SELECT age, sex FROM workers', values),
            t.any('SELECT sex, scores FROM workers GROUP BY sex', values)
        ]);
    })
        .then(data => {
            res.json({rows: data[0], total: data[1]});
        })
        .catch(error => {
            // handle error
        });
}

And that's everything. You don't have to reinvent promise patterns for working with the database, they are all part of the library already.

And if your queries have a dependency, see: How to get results from multiple queries at once .

Or if you prefer ES6 generators:

function queryAll(req, res) {
    db.task(function* (t) {
        let rows = yield t.any('SELECT age, sex FROM workers', values);
        let total = yield t.any('SELECT sex, scores FROM workers GROUP BY sex', values);
        return {rows, total};
    })
        .then(data => {
            res.json(data);
        })
        .catch(error => {
            // handle error
        });
}

And with the ES7's await/async it would be almost the same.

First of all there are some errors in your code, you have to place the reject before the return, otherwise it will be never called, and create a dangling promise:

function searchQuery() {
  return new Promise(function(resolve, reject) {
    var rowData = {
      rows: {}
    };

    pool.query('select age, sex from workers;', values, function(err, result) {
      if (err) {
        reject(err);
        console.error('error running query', err);
      } else {
        rowData.rows = result.rows;
        resolve(rowData);
      }
    });
  });
}

Beside that you should not nest the Promises when ever possible.

So it should be:

 function queryAll(req, res) {
   var result = {}; 
   searchQuery()
     .then((search) => {
       console.log(search);
       result.rows = search;
       return totalQuery();
     })
     .then((total) => {
       result.total = total;
       console.log(total);
     });
 }

The res.json has to be called in the then part of the Promise:

 function queryAll(req, res) {
   var result = {}; 
   searchQuery()
     .then((search) => {
       console.log(search);
       result.rows = search;
       return totalQuery();
     })
     .then((total) => {
       result.total = total;
       console.log(total);
     })
     .then(() => {
       res.json({
         "rows": result.rows,
         "total": result.total
       });
     });
 }

If your queryAll is called as eg middleware of express, then you should handle the catch case within queryAll :

 function queryAll(req, res) {
   var result = {}; 
   searchQuery()
     .then((search) => {
       console.log(search);
       result.rows = search;
       return totalQuery();
     })
     .then((total) => {
       result.total = total;
       console.log(total);
     })
     .then(() => {
       res.json({
         "rows": result.rows,
         "total": result.total
       });
     })
     .catch( err => {
        res.status(500).json({error: 'some error'})
     });
 }

For postgress I would suggest to use pg-promise instead of using a callback style library and wrapping it into promises yourself.

You could simplify the code if you use a library like bluebird :

 const bPromise = require('bluebird')

 function queryAll(req, res) {
   bPromise.all([
     searchQuery(),
     totalQuery()
   ])
   .spread((rows, total) => {
       res.json({
         "rows": rows,
         "total": total
       });
     })
     .catch(err => {
       res.status(500).json({
         error: 'some error'
       })
     });
 }

With nsynjs your logic may be coded as simple as this:

var resp = {
    rows: dbQuery(nsynjsCtx, conn, 'select age, sex from workers', values1).data,
    total: dbQuery(nsynjsCtx, conn, 'select sex, scores from workers group by sex', values2).data
};

Please see example of multiple sequential queries here: https://github.com/amaksr/nsynjs/tree/master/examples/node-mysql

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM