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

时间:2017-05-24 12:10:08

标签: javascript node.js postgresql promise

我最近从回调函数转移到node.js中的 promises 。我想以最优雅的方式对DB(psql)执行异步查询。我想知道以下代码是否是正确的方法,或者我是否能以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);
        });
    });
}

5 个答案:

答案 0 :(得分:3)

var rowData = { rows: {} };
var totalData = { totals: {} };

首先,这些没有意义存储在变量中,因为对象上没有别的东西。只需直接解决行。

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

另外,不要只是console.log您的错误。 then接受第二个回调,该回调在抛出的错误或被拒绝的承诺上执行。将此消息抛出错误或改为拒绝。此外,我会将记录留给消费者。

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

rowstotal并不存在于任何地方。另外,在执行res.json时,rowstotal(假设它们来自回调内部)已经不存在,因为整个序列都是异步的。您将获得未定义的值作为结果。

我认为按顺序运行searchQuerytotalQuery并没有什么意义,因为它们并不相互依赖。相反,并行运行它们会更好。请使用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);
    });
  });
}

答案 1 :(得分:3)

您在代码中遇到一些问题:

  • return执行reject()
  • 之前
  • 有一个未定义的rows变量(与search不匹配)
  • res.json在结果出现之前执行。
  • 承诺解析为{ rows: rows }之类的对象,但主函数似乎期望普通数字,而不是对象。所以,让承诺只解决数值。
  • 第二个SQL不明确,因为第二个字段没有聚合,也没有出现在group by子句中。假设您要对分数求和,请使用sum()
  • 第二个查询仅在第一个查询返回结果后启动,但这可以并行完成
  • 你有非常相似的代码重复。尝试重用代码并将SQL语句作为参数。

以下是我建议的方法:

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);
        });
    });
}

答案 2 :(得分:1)

最优雅的解决方案是通过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
        });
}

这就是一切。您不必重新发明使用数据库的承诺模式,它们都是库的一部分。

如果您的查询有依赖关系,请参阅:How to get results from multiple queries at once

或者如果您更喜欢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
        });
}

使用ES7的await/async它几乎是一样的。

答案 3 :(得分:0)

首先,您的代码中存在一些错误,您必须在返回之前放置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);
      }
    });
  });
}

除此之外,你不应该尽可能地嵌套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);
     });
 }

必须在承诺的res.json部分调用then

 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
       });
     });
 }

如果您的queryAll被称为例如Express的中间件,那么你应该处理catch中的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'})
     });
 }

对于postgress,我建议使用pg-promise而不是使用回调样式库并自己将其包装到promises中。

如果您使用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'
       })
     });
 }

答案 4 :(得分:-1)

使用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
};

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