Node.js MySQL错误处理

Node.js MySQL错误处理,node.js,node-mysql,Node.js,Node Mysql,我已经阅读了几个在node.js中使用mysql的示例,我对错误处理有疑问 大多数示例都会这样处理错误(可能是为了简洁): 这会导致服务器在每次出现sql错误时崩溃。我希望避免这种情况,并保持服务器运行 我的代码如下: app.get('/countries', function(req, res) { pool.createConnection(function(err, connection) { if (err) { console.log

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

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

这会导致服务器在每次出现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
        });
    });
});
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 });
}
我不确定这是不是最好的处理方法。我还想知道在查询的
err
块中是否应该有
connection.release()
。否则,连接可能会保持打开状态,并随着时间的推移而建立


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

我认为您可以这样做。不管怎样,一旦完成查询,连接就会被释放,服务器不会因为错误而崩溃

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

我决定使用es2017语法和Babel来处理它,并将其传输到节点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{}最后{}catch{}模式一起使用
可以进行干净的错误处理,您可以在一个地方收集和处理所有错误。无论发生什么情况,finally块都会关闭数据库连接

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

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

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

这是一个函数,用于在成功连接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包:


本机Promise async/await ES2017

另一个优雅的解决方案是使用
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 ....
     }
   })

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

所以

上面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)
);

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

我想这种方法更容易实现。在这种情况下,即使您无法获得连接,您也会向客户端抛出一个内部服务器错误状态(在构建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');

      });


   });

节点还具有try-catch-finally功能。不过,标准是通过回调传递错误。@TGray我已经读到try-catch在异步代码中有问题。这是坏消息吗?另外,你能举例说明通过回调传递错误是什么意思吗?我发现这是一个有用的参考:@TGray谢谢。我现在仍然确定如何将其应用到我的案例中。谢谢。这种优化有助于避免重复connection.release()代码,但我认为,在更复杂的情况下,这种优化是行不通的。下面是我写的一个更复杂的例子:。这对我来说似乎并不理想。继我之前的评论之后,也许有点进步了。我不知道是否还有更多的事情可以做。轻微的改变:(我不确定为什么我认为我需要通过
res
,但我一开始没有它就会出错)-1因为不能保证
.release()
会被正确调用。还有其他代码可能会导致连接无法关闭。如果有人想查看errors.js或pool.js模块,或者想帮助Babel在es2017上传输,请告诉我。谢谢@Dave的帖子。我目前正在编写一个lambda函数,它使用mysql npm包。我不需要池,只想在表中插入一行。我用嵌套的try-and-catch语句进行了尝试,但我还没有使用任何承诺。问题是,如果我的内部try语句抛出了一个错误,那么它就不会被捕获。我还尝试了两个catch语句,但这也没有帮助。在我的catch语句中,我试图返回一个错误,但这并没有执行。。。哦,我发现了这一点:我意识到我可以在外部catch子句中重新引用错误。不幸的是,抛出错误会使节点应用程序崩溃,并且不会返回任何内容。finally子句被执行,但不是catch子句中的return语句…@merc您不能使用try。。。使用回调式异步编程捕获。您需要承诺和异步/等待。