【问题标题】:Node.js MySQL Error HandlingNode.js MySQL 错误处理
【发布时间】:2016-10-19 20:52:51
【问题描述】:

我已经阅读了几个在 node.js 中使用 mysql 的示例,但我对错误处理有疑问。

大多数示例都是这样处理错误的(可能是为了简洁):

app.get('/countries', function(req, res) {

    pool.createConnection(function(err, connection) {
        if (err) { throw err; }

        connection.query(sql, function(err, results) {
            if (err) { throw err; }

            connection.release();

            // do something with results
        });
    });
});

这会导致服务器在每次出现 sql 错误时崩溃。我想避免这种情况并保持服务器运行。

我的代码是这样的:

app.get('/countries', function(req, res) {

    pool.createConnection(function(err, connection) {
        if (err) {
            console.log(err);
            res.send({ success: false, message: 'database error', error: err });
            return;
        }

        connection.on('error', function(err) {
            console.log(err);
            res.send({ success: false, message: 'database error', error: err });
            return;
        });

        connection.query(sql, function(err, results) {
            if (err) {
                console.log(err);
                res.send({ success: false, message: 'query error', error: err });
                return;
            }

            connection.release();

            // do something with results
        });
    });
});

我不确定这是否是处理它的最佳方式。我还想知道查询的err 块中是否应该有connection.release()。否则,连接可能会保持打开状态并随着时间的推移而建立。

我已经习惯了 Java 的 try...catch...finally 或 try-with-resources,在那里我可以“干净地”捕获任何错误并在最后关闭我的所有资源。有没有办法向上传播错误并在一个地方处理它们?

【问题讨论】:

  • Node 还具有 try-catch-finally 功能。不过,标准是通过回调传递错误。
  • @TGray 我读到 try-catch 在异步代码中存在问题。那是坏信息吗?另外,您能否举例说明通过回调传递错误的含义?
  • 这是我发现有用的参考:docs.nodejitsu.com/articles/errors/…
  • @TGray 谢谢。我现在仍然确定如何将其应用于我的案例。

标签: node.js node-mysql


【解决方案1】:

我决定使用 es2017 语法和 Babel 来处理它以转换为 Node 7 支持的 es2016。

较新版本的 Node.js 无需转译即可支持此语法。

这是一个例子:

'use strict';

const express = require('express');
const router = express.Router();

const Promise = require('bluebird');
const HttpStatus = require('http-status-codes');
const fs = Promise.promisifyAll(require('fs'));

const pool = require('./pool');     // my database pool module, using promise-mysql
const Errors = require('./errors'); // my collection of custom exceptions


////////////////////////////////////////////////////////////////////////////////
// GET /v1/provinces/:id
////////////////////////////////////////////////////////////////////////////////
router.get('/provinces/:id', async (req, res) => {

  try {

    // get a connection from the pool
    const connection = await pool.createConnection();

    try {

      // retrieve the list of provinces from the database
      const sql_p = `SELECT p.id, p.code, p.name, p.country_id
                     FROM provinces p
                     WHERE p.id = ?
                     LIMIT 1`;
      const provinces = await connection.query(sql_p);
      if (!provinces.length)
        throw new Errors.NotFound('province not found');

      const province = provinces[0];

      // retrieve the associated country from the database
      const sql_c = `SELECT c.code, c.name
                     FROM countries c
                     WHERE c.id = ?
                     LIMIT 1`;
      const countries = await connection.query(sql_c, province.country_id);
      if (!countries.length)
        throw new Errors.InternalServerError('country not found');

      province.country = countries[0];

      return res.send({ province });

    } finally {
      pool.releaseConnection(connection);
    }

  } catch (err) {
    if (err instanceof Errors.NotFound)
      return res.status(HttpStatus.NOT_FOUND).send({ message: err.message }); // 404
    console.log(err);
    return res.status(HttpStatus.INTERNAL_SERVER_ERROR).send({ error: err, message: err.message }); // 500
  }
});


////////////////////////////////////////////////////////////////////////////////
// GET /v1/provinces
////////////////////////////////////////////////////////////////////////////////
router.get('/provinces', async (req, res) => {

  try {

    // get a connection from the pool
    const connection = await pool.createConnection();

    try {

      // retrieve the list of provinces from the database
      const sql_p = `SELECT p.id, p.code, p.name, p.country_id
                     FROM provinces p`;
      const provinces = await connection.query(sql_p);

      const sql_c = `SELECT c.code, c.name
                     FROM countries c
                     WHERE c.id = ?
                     LIMIT 1`;

      const promises = provinces.map(async p => {

        // retrieve the associated country from the database
        const countries = await connection.query(sql_c, p.country_id);

        if (!countries.length)
          throw new Errors.InternalServerError('country not found');

        p.country = countries[0];

      });

      await Promise.all(promises);

      return res.send({ total: provinces.length, provinces });

    } finally {
      pool.releaseConnection(connection);
    }

  } catch (err) {
    console.log(err);
    return res.status(HttpStatus.INTERNAL_SERVER_ERROR).send({ error: err, message: err.message }); // 500
  }
});


////////////////////////////////////////////////////////////////////////////////
// OPTIONS /v1/provinces
////////////////////////////////////////////////////////////////////////////////
router.options('/provinces', async (req, res) => {
  try {
    const data = await fs.readFileAsync('./options/provinces.json');
    res.setHeader('Access-Control-Allow-Methods', 'HEAD,GET,OPTIONS');
    res.setHeader('Allow', 'HEAD,GET,OPTIONS');
    res.send(JSON.parse(data));
  } catch (err) {
    res.status(HttpStatus.INTERNAL_SERVER_ERROR).send({ error: err, message: err.message });
  }
});


module.exports = router;

将async/await 与try { try { } finally { } } catch { } pattern 一起使用,可以实现清晰的错误处理,您可以在一个地方收集和处理所有错误。 finally 块无论如何都会关闭数据库连接。

您只需要确保自始至终都在处理承诺。对于数据库访问,我使用promise-mysql 模块而不是普通的mysql 模块。对于其他一切,我使用bluebird 模块和promisifyAll()。

我也有自定义的异常类,我可以在某些情况下抛出它们,然后在 catch 块中检测它们。根据 try 块中可能引发的异常,我的 catch 块可能如下所示:

catch (err) {
  if (err instanceof Errors.BadRequest)
    return res.status(HttpStatus.BAD_REQUEST).send({ message: err.message }); // 400
  if (err instanceof Errors.Forbidden)
    return res.status(HttpStatus.FORBIDDEN).send({ message: err.message }); // 403
  if (err instanceof Errors.NotFound)
    return res.status(HttpStatus.NOT_FOUND).send({ message: err.message }); // 404
  if (err instanceof Errors.UnprocessableEntity)
    return res.status(HttpStatus.UNPROCESSABLE_ENTITY).send({ message: err.message }); // 422
  console.log(err);
  return res.status(HttpStatus.INTERNAL_SERVER_ERROR).send({ error: err, message: err.message });
}

pool.js:

'use strict';

const mysql = require('promise-mysql');

const pool = mysql.createPool({
  connectionLimit: 100,
  host: 'localhost',
  user: 'user',
  password: 'password',
  database: 'database',
  charset: 'utf8mb4',
  debug: false
});


module.exports = pool;

errors.js:

'use strict';

class ExtendableError extends Error {
  constructor(message) {
    if (new.target === ExtendableError)
      throw new TypeError('Abstract class "ExtendableError" cannot be instantiated directly.');
    super(message);
    this.name = this.constructor.name;
    this.message = message;
    Error.captureStackTrace(this, this.contructor);
  }
}

// 400 Bad Request
class BadRequest extends ExtendableError {
  constructor(m) {
    if (arguments.length === 0)
      super('bad request');
    else
      super(m);
  }
}

// 401 Unauthorized
class Unauthorized extends ExtendableError {
  constructor(m) {
    if (arguments.length === 0)
      super('unauthorized');
    else
      super(m);
  }
}

// 403 Forbidden
class Forbidden extends ExtendableError {
  constructor(m) {
    if (arguments.length === 0)
      super('forbidden');
    else
      super(m);
  }
}

// 404 Not Found
class NotFound extends ExtendableError {
  constructor(m) {
    if (arguments.length === 0)
      super('not found');
    else
      super(m);
  }
}

// 409 Conflict
class Conflict extends ExtendableError {
  constructor(m) {
    if (arguments.length === 0)
      super('conflict');
    else
      super(m);
  }
}

// 422 Unprocessable Entity
class UnprocessableEntity extends ExtendableError {
  constructor(m) {
    if (arguments.length === 0)
      super('unprocessable entity');
    else
      super(m);
  }
}

// 500 Internal Server Error
class InternalServerError extends ExtendableError {
  constructor(m) {
    if (arguments.length === 0)
      super('internal server error');
    else
      super(m);
  }
}


module.exports.BadRequest = BadRequest;
module.exports.Unauthorized = Unauthorized;
module.exports.Forbidden = Forbidden;
module.exports.NotFound = NotFound;
module.exports.Conflict = Conflict;
module.exports.UnprocessableEntity = UnprocessableEntity;
module.exports.InternalServerError = InternalServerError;

【讨论】:

  • 如果有人想查看 errors.js 或 pool.js 模块,或者想帮助 Babel 转译 es2017,请告诉我。
  • 感谢@Dave 的帖子。我目前正在编写一个使用 mysql npm 包的 lambda 函数。我不需要池,只想在表中插入一行。我用你的嵌套 try 和 catch 语句尝试了它,但我没有使用任何承诺(还)。问题是,如果我的内部 try 语句抛出错误,则不会被捕获。我还尝试了两个 catch 语句,但这也没有帮助。在我的 catch 语句中,我试图返回一个错误,但这没有执行......?‍♂️。知道为什么吗?
  • 哦,我发现了这个:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… 我意识到我可以重新抛出错误以到达外部 catch 子句。不幸的是,抛出错误会使节点应用程序崩溃,并且没有返回任何内容。 finally 子句被执行但不是 catch 子句中的 return 语句...
  • @merc 你不能在回调式异步编程中使用 try ... catch。为此,您需要 promises 和 async/await。
【解决方案2】:

这是一个在成功连接 MySQL 时返回可用池的函数。所以在我进行任何查询之前,我会等待这个函数来检查连接是否正常。即使没有与 MySQL 的连接,这也不会导致服务器崩溃。

connect: function ()
    {
        return new Promise((resolve, reject) => {
            let pool = Mysql.createPool({
                connectionLimit: config.mysql.connectionLimit,
                host: config.mysql.host,
                user: config.mysql.user,
                password: config.mysql.password,
                database: config.mysql.database
            });

            pool.getConnection((err, con) =>
            {
                try
                {
                    if (con)
                    {
                        con.release();
                        resolve({"status":"success", "message":"MySQL connected.", "con":pool});
                    }
                }
                catch (err)
                {
                    reject({"status":"failed", "error":`MySQL error. ${err}`});
                }
                resolve({"status":"failed", "error":"Error connecting to MySQL."});
            });
        });
    }

使用的 MySQL 包:https://www.npmjs.com/package/mysql

Native Promise 异步/等待 ES2017

【讨论】:

    【解决方案3】:

    为了处理从 sql 连接返回的特定错误处理情况,您可以查看从回调返回的“错误”对象。

    所以..

    const mysql = require('mysql') 
    
    let conn = mysql.createConnection(connConfig)
    
    conn.query(query, function(error, result, fields){
        if (error){
            console.log(typeof(error));
            for(var k in error){
                console.log(`${k}: ${error[k]}`)
            }
    }
    

    上面 for 循环中的 console.log 语句将输出如下内容:

    对象

    code: ER_TABLE_EXISTS_ERROR
    errno: 1050
    sqlMessage: Table 'table1' already exists
    sqlState: 42S01
    index: 0
    sql: CREATE TABLE table1 (
    PersonID int,
    LastName varchar(255),
    FirstName varchar(255),
    City varchar(255)
    );
    

    使用这些键,您可以将值传递给处理程序

    【讨论】:

      【解决方案4】:

      我认为你可以做这样的事情。无论如何,一旦查询完成,连接就会被释放,服务器不会因为错误而崩溃。

      var queryString = "SELECT * FROM notification_detail nd LEFT JOIN notification n ON nd.id_notification = n.uuid WHERE login_id = ?  id_company = ?;";
      var filter = [loginId, idCompany];
      
      var query = connection.query({
          sql: queryString,
          timeout: 10000,
      }, filter );
      
      query
        .on('error', function(err) {
         if (err) {
            console.log(err.code);
            // Do anything you want whenever there is an error.
            // throw err;
         } 
      })
      .on('result', function(row) {
        //Do something with your result.
      })
      .on('end', function() {
        connection.release();
      });
      

      这可以是一个更简单的替代解决方案。

      var query = connection.query({
      sql: queryString, 
      timeout: 10000,
      }, function(err, rows, fields) {
          if (err) {
            //Do not throw err as it will crash the server. 
            console.log(err.code);
          } else {
            //Do anything with the query result
          } 
          connection.release()
      });
      

      【讨论】:

      • 谢谢。这种优化有助于避免重复 connection.release() 代码,但我认为这在更复杂的情况下会分崩离析。这是我写的一个更复杂的例子:pastebin.com/EDKLX5SK。这对我来说似乎并不理想。
      • 在我之前的评论之后,pastebin.com/R5B2yLKj 可能会有所改进。我不知道是否还有更多可以做的。
      • 轻微变化:pastebin.com/DnamtjrM(我不知道为什么我认为我需要通过res,但一开始没有它我得到一个错误)
      • -1 因为不能保证.release() 会被正确调用。还有其他代码可能导致连接永远不会关闭。
      【解决方案5】:

      另一个优雅的解决方案是使用async.series,以及它管理错误的方式

      const mysql = require('mysql') 
      const async = require('async')
      
      async.series([
        function (next) {
          db = mysql.createConnection(DB_INFO)
          db.connect(function(err) {
            if (err) {
              // this callback/next function takes 2 optional parameters: 
              // (error, results)
              next('Error connecting: ' + err.message)
            } else {
              next() // no error parameter filled => no error
            }
          })
        },
        function (next) {
           var myQuery = ....
           db.query(myQuery, function (err, results, fields) {
             if (err) {
               next('error making the query: ' + err.message)
               return // this must be here
             }
             // do something with results
             // ...
             next(null, results) // send the results
           })
         },
         function (next) {
           db.close()
         }], 
         //done after all functions were executed, except if it was an error 
         function(err, results) {
           if (err) {
             console.log('There was an error: ', err)
           }
           else {
             //read the results after everything went well
             ... results ....
           }
         })
      

      【讨论】:

        【解决方案6】:

        我想这种方法更平易近人。在这种情况下,即使您无法获得连接,您也会向客户端抛出内部服务器错误状态(如果您构建 Rest Api 服务器会很有帮助),并且如果在释放连接后出现查询错误,您会发送错误。如果在任何地方错了,请纠正我。

         pool.getConnection(function(err, connection){
              if(err){
                console.log(err);
                return res.status(500).json();
              };
        
        
              connection.query('SELECT * FROM TABLE', function(err,results,fields){
                connection.release();
        
                if(err){
                  console.log(err);
                  return (res.status(500).json());
                };
                res.status(201).send('OK');
        
              });
        
        
           });
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-07-13
          • 2017-03-02
          • 1970-01-01
          • 1970-01-01
          • 2014-03-13
          • 2013-03-20
          • 1970-01-01
          相关资源
          最近更新 更多