【问题标题】:how to properly send the response code from Node.js API如何从 Node.js API 正确发送响应代码
【发布时间】:2016-08-30 04:34:32
【问题描述】:

我有一个简单的基于节点的 API,它需要解析一些 JSON,将一些数据保存到 Postgres,然后发送适当的响应代码(如 http 201)。 我的代码如下所示:

router.route('/customer')

    .post(function(req, res) {
        Customers = req.body;
        var numberOfCustomers = Customers.length;
        for(var i = 0; i < Customers.length; i++){
            Customer = Customers[i];
            console.log(Customer.Name  + "  " + Customer.Address);
            var date = moment(new Date()).unix();

            client.query(
                'INSERT into customer (name, address, date_modified) VALUES($1, $2, $3) RETURNING id',
                [Customer.Name, Customer.Address, date],
                function(err, result) {
                    if (err) {
                        console.log(err);
                        status = 1;
                    } else {
                        console.log('row inserted with id: ' + result.rows[0].id);
                        if(numberOfCustomers === i) {
                            res.status(201).send({ message: "created" });
                        }
                    }
                });
        }
    })

我收到此错误:

_

http_outgoing.js:344
    throw new Error('Can\'t set headers after they are sent.');
    ^

Error: Can't set headers after they are sent.
    at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:344:11)

我需要考虑这样一个事实,即我在一个循环中多次执行 Postgres 插入,所以在第一次插入完成后我无法发送响应标头。

我的“POST”处理程序中最适合放置我的res.status(201).send({ message: "created" }); 的位置是什么? ?

【问题讨论】:

  • 最合适的位置是您希望向请求发送单个响应的位置。这完全取决于您的要求。

标签: javascript sql node.js postgresql


【解决方案1】:

除了架构决策(例如,您可能需要一个单独的模块作为 HTTP 适配器来处理发送响应代码的逻辑,而不是在路由控制器内部执行),您可以使用 Promise 来等待所有插入完成,然后发送一个 single 响应代码。例如,像这样:

var Promise = require('bluebird');
var query = Promise.promisify(client.query);

router.route('/customer')
.post(function(req, res) {
   // all your logic, and then

   return Promise.all(Customers.map(function() {
     return query(sql, [Customer.Name, Customer.Address, date]);
   })
   .then(function() {
     res.status(201).send({ message: 'Created' });
   });
});

查看the bluebird docs 以获取此示例中使用的 API。

我不熟悉 Postgres 的 API,但概念应该类似:您需要等待所有对您的 DB 的请求都被首先解决。

【讨论】:

  • 为什么不直接使用 Promise 的原生实现?
  • @cbass 如果您使用的是最新的 node.js,那么是的,您可以使用 Promise 的本机实现。或者,如果您使用的是旧版本并希望将 es6 转换为 es5,这也是一种选择。
【解决方案2】:

如上所述:是的,诸如 Promises 和 async 之类的异步助手对此类事务很有帮助。但是,我确实相信解决这个问题的“最佳”方法是只使用一个查询。而不是每个查询只执行一次插入,而是将它们全部批处理到一个查询中,如下所示:

INSERT into customer (name, address, date_modified)
VALUES
   ($1, $2, $3),
   ($4, $5, $6),
   ($7, $8, $9),
   ...
RETURNING id'

建议

router.route('/customer').post(function(req, res) {

    //Fetch customers
    var customers = req.body;

    //Store parameters and query inserts for db-query.
    var params = [];
    var inserts = [];

    //For each customer
    // - Add parameters for query
    // - Build insert string
    customers.forEach(function(customer){
       inserts.push(
         [
           "($",
           params.push(customer.Name),
           ", $",
           params.push(customer.Address),
           ", ",
           NOW(), //unnecessary to generate timestamp in js
           ")",
         ].join('')
       )
    });

    //Build query
    var query = 'INSERT into customer (name, address, date_modified) VALUES '+ inserts +' RETURNING id';

    //Query database in a more simple fashion.
    client.query(query, params, function(err, result) {
        if (err) {
            console.log(err);
            status = 1;
        } else {
            res.status(201).send({ message: "created" });
        });
    }
})

如果您使用的是 ES6,则可以通过使用字符串模板来简化字符串构建操作。

customers.forEach(function(customer){
    var query = `($${params.push(customer.Name)}, $${params.push(customer.Address)}, NOW())`
    inserts.push(query);
});

//and

var query = `
    INSERT into customer (name, address, date_modified)
    VALUES ${inserts}
    RETURNING id
`;

【讨论】:

    【解决方案3】:

    正确的做法是,我也建议您查看Async or lodash lib。

    router.route('/customer')
    
    .post(function(req, res) {
        var Customers = req.body,
            numberOfCustomers = Customers.length;
    
        for(var i = 0; i < Customers.length; i++){
            var Customer = Customers[i];
            console.log(Customer.Name  + "  " + Customer.Address);
            var date = moment(new Date()).unix(),
                sql = 'INSERT into customer (name, address, date_modified) VALUES($1, $2, $3) RETURNING id';
    
            client.query(sql, [Customer.Name, Customer.Address, date],
                function(err, result) {
                    if (err) {
                        console.log(err);
                        res.status(500).json({message: "Server Error", err: err});
                    } else {
                        console.log('row inserted with id: ' + result.rows[0].id);
                        if (numberOfCustomers === i) {
                            res.status(201).send({ message: "Created" });
                        }
                    }
                });
        }
    })
    

    【讨论】:

    • 不幸的是,这将导致 OP 看到的相同问题。您正在循环中更改标头。所以在第一次迭代之后,你会得到一个错误
    • True @Seth 但是 if 块只会在出现错误时返回,再加上发回 error stack,对于无法处理的 Array 中的特定 object .我认为也应该有一个出口以保持数据库清洁
    • 如果第一次迭代成功,第二次失败怎么办?标头将已经设置并且响应在此时终止,再次导致现有问题。
    猜你喜欢
    • 2019-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-14
    • 1970-01-01
    • 2016-08-03
    • 1970-01-01
    相关资源
    最近更新 更多