Node.js MySQL 错误处理
Node.js MySQL Error Handling
我在 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
,在那里我可以 "cleanly" 捕获任何错误并在最后关闭我的所有资源。有没有办法向上传播错误并在一个地方处理它们?
我认为你可以做这样的事情。不管怎样,只要查询完就会释放连接,不会因为错误导致服务器崩溃。
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 来处理它以向下转换为 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;
这是 MySQL 连接成功后 return 可用池的函数。所以在我继续任何查询之前,我会等待这个函数来检查连接是否正常。即使没有连接到 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
原生承诺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' 对象。
所以..
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)
);
使用这些键,您可以将值传递给处理程序
我觉得这个方法比较平易近人。在这种情况下,即使你无法获得连接,你也会向客户端抛出一个内部服务器错误状态(如果你构建一个 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');
});
});
我在 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
,在那里我可以 "cleanly" 捕获任何错误并在最后关闭我的所有资源。有没有办法向上传播错误并在一个地方处理它们?
我认为你可以做这样的事情。不管怎样,只要查询完就会释放连接,不会因为错误导致服务器崩溃。
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 来处理它以向下转换为 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;
这是 MySQL 连接成功后 return 可用池的函数。所以在我继续任何查询之前,我会等待这个函数来检查连接是否正常。即使没有连接到 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
原生承诺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' 对象。
所以..
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)
);
使用这些键,您可以将值传递给处理程序
我觉得这个方法比较平易近人。在这种情况下,即使你无法获得连接,你也会向客户端抛出一个内部服务器错误状态(如果你构建一个 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');
});
});