关于Nodejs异常处理的一个教训,再次理解什么是异步回调.
关于Nodejs异常处理的一个教训,再次理解什么是异步回调.
环境:express+sequelize 代码的意图是统一处理router中的异常,想当然的写了段try/catch,
var m = ['get','post','put','delete'];
m.forEach(function(type){
var handlers = app.routes[type];
if(handlers){
handlers.forEach(function(handler){
//TODO:need to config the excluded handler path,not the hard code.
if(handler.path !== '/' && handler.path !== '/auth'){//the '/auth' is the login url.
handler.callbacks = [authIdentity].concat(handler.callbacks);
}
handler.callbacks.forEach(function(callback){
var packageCallBack = function(req,res,next){
try{callback(req,res,next);}
catch(e){
debugger;
//TODO:need to implement other logics.
if(e instanceof AppError){
res.send(e);
}
next(e);
}
};
callback = packageCallBack;
});
});
}
});
在处理请求的方法中,想当然的抛异常.
exports.authenticate= function(req, res, next){
var daos = app.get('models')
,user = daos.user
,_vaule = {};
user.find({where:{login:req.param('login')}}).success(
function(value){
if(value){
var result = md5.validate(value.hashed_password,req.param('passwd'));
if(result){
req.session.userinfo = value.login;
var url = req.param('url');
if(url){
res.redirect(url);
}
}else{
throw new AppError('invalid user or passwd.');
//next(new AppError('invalid user or passwd.'));
}
}
else{
throw new AppError('the user does not exist.');
//next(new AppError('the user does not exist.'));
}
});
};
期望就是抛出的异常,有外围的try/catch去捕获,相信很多初学者都会犯类似的错误,在异步回调的时空里,回调方法抛出异常,往往就是噩梦,因为回调方法里的异常,try有啥用?
个人有个不恰当的比喻: node就像是一根绳子,异步回调是无处不在的绳子节点,抛异常好比剪刀,一刀下去,绳子断了. 最终的处理方式,已经很明显了,使用express中的next去传递异常,而不是抛~ 这里强调一下,回调和异步回调不是同一个概念,这里有篇文章讲解了回调捕获异常和异步回调捕获异常的区别 http://www.html-js.com/archives/category/nodejs/page/2
下面几个链接讲述了node中异常处理的基本方式: http://snmaynard.com/2012/12/21/node-error-handling/ http://stackoverflow.com/questions/7151487/error-handling-principles-for-nodejs-express-apps http://stackoverflow.com/questions/7310521/node-js-best-practice-exception-handling
关于Nodejs异常处理的一个教训,再次理解什么是异步回调
在Node.js中,处理异常的方式与传统的同步代码有所不同。尤其是在涉及到异步回调时,异常处理变得更加复杂。本文通过一个实际的例子来说明这个问题,并提出解决方案。
环境
本例使用的是express
框架配合sequelize
ORM。
问题描述
我们的目标是在路由处理中统一处理异常。最初的想法是使用try/catch
块来捕获异常:
var m = ['get', 'post', 'put', 'delete'];
m.forEach(function (type) {
var handlers = app.routes[type];
if (handlers) {
handlers.forEach(function (handler) {
if (handler.path !== '/' && handler.path !== '/auth') {
handler.callbacks = [authIdentity].concat(handler.callbacks);
}
handler.callbacks.forEach(function (callback) {
var packageCallback = function (req, res, next) {
try {
callback(req, res, next);
} catch (e) {
debugger;
if (e instanceof AppError) {
res.send(e);
}
next(e);
}
};
callback = packageCallback;
});
});
}
});
然而,在处理请求的方法中,我们发现即使在try/catch
块内抛出异常,也无法被捕获:
exports.authenticate = function (req, res, next) {
var daos = app.get('models'),
user = daos.user,
_value = {};
user.find({ where: { login: req.param('login') } }).success(
function (value) {
if (value) {
var result = md5.validate(value.hashed_password, req.param('passwd'));
if (result) {
req.session.userinfo = value.login;
var url = req.param('url');
if (url) {
res.redirect(url);
}
} else {
throw new AppError('invalid user or passwd.');
// next(new AppError('invalid user or passwd.'));
}
} else {
throw new AppError('the user does not exist.');
// next(new AppError('the user does not exist.'));
}
}
);
};
原因分析
在异步回调中抛出的异常并不会被try/catch
捕获。这是因为异步回调是在事件循环的其他阶段执行的,而此时try/catch
已经结束了。
解决方案
正确的做法是使用next
函数将异常传递给Express中间件链。例如:
exports.authenticate = function (req, res, next) {
var daos = app.get('models'),
user = daos.user,
_value = {};
user.find({ where: { login: req.param('login') } }).success(
function (value) {
if (value) {
var result = md5.validate(value.hashed_password, req.param('passwd'));
if (result) {
req.session.userinfo = value.login;
var url = req.param('url');
if (url) {
res.redirect(url);
}
} else {
return next(new AppError('invalid user or passwd.'));
}
} else {
return next(new AppError('the user does not exist.'));
}
},
function (err) {
if (err) {
return next(err);
}
}
);
};
这样,Express会自动处理这些异常并调用相应的错误处理中间件。
总结
在Node.js中,处理异步回调中的异常需要特别注意。直接抛出异常通常不会被try/catch
捕获,而是应该使用next
函数来传递异常。这不仅符合Node.js的设计理念,还能更好地管理应用的错误处理逻辑。
在Node.js中,异步回调的异常处理与同步代码的处理方式不同。在同步代码中,可以使用try/catch
来捕获异常,但在异步回调中,这种方式不起作用。这是因为当回调函数执行时,它所在的上下文已经被改变,而try/catch
只能捕获当前上下文中的异常。
在你的例子中,尝试在authenticate
方法的回调函数内部抛出异常,但这样会导致异常无法被捕获。正确的做法是在回调函数中调用next
函数来传递异常,让Express框架来处理这些异常。
示例代码
const express = require('express');
const app = express();
const sequelize = require('sequelize');
app.use(express.json());
// 模拟模型
const models = {
user: {
find: (query, callback) => {
const users = [{ id: 1, login: 'admin', hashed_password: 'hashed_admin_pass' }];
const foundUser = users.find(user => user.login === query.where.login);
callback(foundUser);
}
}
};
app.set('models', models);
const authIdentity = (req, res, next) => {
next();
};
function authenticate(req, res, next) {
const user = models.user;
const value = {};
user.find({ where: { login: req.body.login } }, (err, foundUser) => {
if (err) return next(err);
if (foundUser) {
const isValidPassword = md5.validate(foundUser.hashed_password, req.body.password);
if (isValidPassword) {
req.session.userinfo = foundUser.login;
const url = req.body.url;
if (url) {
res.redirect(url);
}
} else {
return next(new AppError('invalid user or passwd.'));
}
} else {
return next(new AppError('the user does not exist.'));
}
});
}
app.post('/login', authenticate, (req, res) => {
res.send('Login successful');
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
const md5 = {
validate: (hashed, input) => hashed === md5.hash(input)
};
md5.hash = (input) => {
return 'hashed_' + input;
};
const AppError = (message) => {
this.message = message;
this.name = 'AppError';
};
const server = app.listen(3000, () => {
console.log('Server is running on port 3000');
});
解释
-
authenticate
方法:在这个方法中,我们模拟了一个异步查找用户的过程。如果找到用户,则验证密码。如果密码无效或用户不存在,通过调用next
函数并传递一个错误对象来传递异常。 -
错误处理中间件:在最后定义了一个错误处理中间件,用于捕获并处理所有未被捕捉到的异常。
通过这种方式,你可以确保在异步回调中抛出的异常能够被正确地处理。