简体   繁体   English

Node.js-如何以正确的方式链接Promise

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

I recently moved from callback functions to promises in node.js. 我最近从回调函数转到了node.js中的promises I want to preform async query to the DB (psql) in the most elegant way. 我想以最优雅的方式向数据库(psql)执行异步查询。 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) . 我想知道下面的代码是否是正确的方法,或者是否可以以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. 另外,不要只是console.log您的错误。 then accepts a second callback that executes on thrown errors or rejected promises. then接受第二个回调,该回调对抛出的错误或被拒绝的承诺执行。 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. rowstotal在任何地方都不存在。 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. 另外,在执行res.jsonrowstotal (假设它们来自回调内部)将不存在,因为整个序列都是异步的。 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. 我发现按顺序运行searchQuerytotalQuery ,因为它们彼此不依赖。 It's better to run them parallel instead. 最好是并行运行它们。 Use Promise.all for that. Promise.all使用Promise.all

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() 您在执行reject()之前return
  • There is an undefined rows variable (mismatch with search ) 有一个未定义的rows变量(与search不匹配)
  • res.json is executed before the results are in. res.json在结果输入之前执行。
  • The promises resolve to objects like { rows: rows } , but the main function seems to expect the plain numbers, not the objects. Promise可以解析为{ rows: rows }类的对象,但是main函数似乎期望使用纯数字,而不是对象。 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. 第二个SQL模棱两可,因为第二个字段没有聚合,也没有出现在group by子句中。 Assuming you want to sum the scores, use sum() . 假设您想对分数sum() ,请使用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. 尝试重用代码并使SQL语句成为参数。

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 : 最优雅的解决方案是通过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: 或者,如果您更喜欢ES6生成器:

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. 而ES7的await/async几乎是一样的。

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: 首先,您的代码中存在一些错误,您必须将reject放置在返回之前,否则它将永远不会被调用,并创建一个悬挂的承诺:

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. 此外,尽可能不要嵌套Promises。

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: 必须在Promise的then部分中调用res.json

 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 : 如果您的queryAll被称为例如express的中间件,那么您应该在queryAll处理catch情况:

 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. 对于postgress,我建议使用pg-promise而不是使用回调样式库并将其包装到promises中。

You could simplify the code if you use a library like bluebird : 如果您使用诸如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: 使用nsynjs,您的逻辑可以像这样简单地编码:

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 请在此处查看多个顺序查询的示例: https : //github.com/amaksr/nsynjs/tree/master/examples/node-mysql

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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