闽公网安备 35020302035485号
上面提的的这些异常需要我们足够了解自己的程序,对这些可能会出问题的地方做异常捕,当这些异常发生时能返回给客户端一个“可控”的结果,也能让我们对自己的程序更可控。下面先来看看常见的异常。
const user = {
name: 'max'
}
console.log(user.hello)
没定义的东西不要乱取,取的时候也可以考虑用更安全的方式比如 lodash 的 get。const data = getFromServer() data.user = 'abc'不要相信 server 的数据结构,先判断有没有 user,后端数据结构给错了是他的问题,但是程序因为这个崩了就是我们异常没处理的问题。
console.log(typeof null); // 输出 object console.log(typeof undefined); // 输出 undefinednull 和 undefined 的不要傻傻分不清楚, ?? 操作符是用来做什么的?
console.log("a" / 2); // 输出 NaN
看到一些网站上经常出现 NaN,这也是计算杀手,必须判断数据是数字后再计算,不然 NaN 会给用户脑袋中出现一个大大的问号,包括 parseInt("abc") 输出是什么?var arr = [1, 2, 3]; console.log(arr[3]); // 输出 undefined越界应该不能吧?
const fs = require('fs');
const write = function () {
fs.mkdir('./writeFolder');
fs.writeFile('./writeFolder/foobar.txt', 'Hello World');
}
write();
这代码一定报错,fs.mkdir和 fs.writeFile 都是异步方法,第3行代码没执行完,第四条命令就开始运行了即目录都没有创建好,就开始往这个目录下的文件里写内容导致程序崩溃。怎么解?改成顺序调用?即创建目录后再调用写文件?const fs = require('fs');
const write = function () {
fs.mkdir('./writeFolder', ()=>{
fs.writeFile('./writeFolder/foobar.txt', 'Hello World');
});
}
write();
这代码还是有问题,因为你也不确定这个目录有没有创建成功,我们需要记住一点 Node.js 的异步函数都是遵从 Error irst 原则,即如果这个异步操作失败, 第一个参数就会返回 error 值。const fs = require('fs');
const write = function (callback) {
fs.mkdir('./writeFolder', (err, data) => {
if (data)
fs.writeFile('./writeFolder/foobar.txt', 'Hello World!');
else
callback(err) });
}
// 堆代码 duidaima.com
write(console.log);
这样就没问题了吗? 这个 write 函数还是有问题,我们只处理了 fs.mkdir 的错误,当 fs.writeFile 发生错误的话,我们的错误就被“吞掉了”。当然,我们的目标不是通过 if else 来处理 callback 的错误,而是把 callback 的异步方法封装成 promise,来使用更加语义和现代的 catch, 封装一个promise人人都会吧?不会的话参考这个例子封装个异步 sleep 函数练练手?function doesWillNotAlwaysSettle(arg) {
return new Promise((resolve, reject) => {
doATask(foo, (err) => {
if (err) {
return reject(err);
}
if (arg === true) {
resolve('I am Done')
}
});
});
}
这里要提醒的是,千万别在第五行画蛇添足返回一个 字符串 类型,最佳实践是返回 error 对象 或者再 wrap 一个 error 对象,否则会 say goodbye to stack trace。function readTemplate() {
return new Promise(() => {
databaseGet('query', function(err, data) {
if (err) {
reject('Template not found. Error: ', + err);
} else {
resolve(data);
}
});
});
}
readTemplate();
Promise Hell?'use strict';
const fs = require('fs').promises;
const write = function () {
return fs.mkdir('./writeFolder').then(() => {
fs.writeFile('./writeFolder/foobar.txt', 'Hello world!').then(() => {
// do something
}).catch((err) => {
console.error(err);
})
}).catch((err) => {
// catch all potential errors
console.error(err)
})
}
看着可怕不?怎么解决呢?用 async/await , 除了有这个好处外,还有一个好处!const p = new Promise((resolve, reject) => { throw new Error('同步错误'); });
p.catch(error => console.log(error));
p.then(() => console.log('已解决'));
观察上面代码,那是不是第3行是 dead zone? resolve 后这个 promise的状态已经变了,无法再改变。所以写代码时候不要搞这种心智负担,直接 try catch,通过将同步代码包装在 try/catch 块中,我们可以在出现任何错误的情况下正确拒绝 promise。这确保了所有错误(同步和异步)都得到正确处理。
router.get('/:id', function (req, res, next) {
database.getData(req.params.userId)
.then(function (data) {
if (data.length) {
res.status(200).json(data);
} else {
res.status(404).end();
}
})
.catch(() => {
log.error('db.rest/get: could not get data: ', req.params.userId);
res.status(500).json({error: 'Internal server error'});
})
});
像这个代码就问题很大,直接返回 500 服务器异常,太不友好了!你返回 400,给出错误原因,客户端知道是他自己的问题,500 会给所有人带来“沟通的痛苦”async function foobar() {
throw new Error('foobar');
}
async function baz() {
throw new Error('baz')
}
(async function doThings() {
const a = foobar();
const b = baz();
try {
await a;
await b;
} catch (error) {
// ignore all errors!
}
})();
咋办?上Promise all呗(async function doThings() {
const a = foobar();
const b = baz();
try {
await Promise.all([a, b]);
} catch (error) {
// ignore all errors!
}
})();
还有一种情况非常容易出现 unhandled rejectionsasync function foobar() {
throw new Error('foobar');
}
async function doThings() {
try {
return foobar()
} catch {
// ignoring errors again !
}
}
doThings();
第七行明显没有处理错误,因为没 await,没明白? 看这篇 你知道吗?在try/catch块之外,return await是多余的。
class ApplicationError extends Error {
get name() {
return this.constructor.name;
}
}
class DatabaseError extends ApplicationError { }
class UserFacingError extends ApplicationError { }
module.exports = {
ApplicationError,
DatabaseError,
UserFacingError
}
有了这 3 个类以后,我们就可以开始处理具体异常了,举一个用户查询不到的异常吧,其他异常大家可以自己发散,(比如连接数据库挂了,call第三方接口挂了,代码runtime挂了,各种)。class NotFoundError extends UserFacingError {
constructor(message, options = {}) {
super(message);
// You can attach relevant information to the error instance
// (e.g.. the username)
for (const [key, value] of Object.entries(options)) {
this[key] = value;
}
}
get statusCode() {
return 404
}
}
接下来就把我们定义的错误 用起来app.get('/:id', async function (req, res, next) {
let data
try {
data = await database.getData(req.params.userId)
} catch (err) {
return next(err);
}
if (!data.length) {
// 我们用刚才定义的类来处理 用户没查到异常
return next(new NotFoundError('Dataset not found'));
}
res.status(200).json(data)
})
app.use(function (err, req, res, next) {
// 堆代码 duidaima.com
// 判断是哪一类错误
if (err instanceof UserFacingError) {
res.sendStatus(err.statusCode);
res.status(err.statusCode).send(err.errorCode)
} else {
res.sendStatus(500)
}
// 必须 log 异常
logger.error(err, 'Parameters: ', req.params, 'User data: ', req.user)
});
这里有2个建议,demo 这么写 ok,但是工程里要分层处理错误。如果在任何地方处理错误,那么对错误处理的方法就是不一致的,代码又乱又难以追踪,其次就确保你抛出的错误类是你唯一异常原因来源,并且包含了调试应用程序所需的所有信息,不然你的错抛出来也无法定位到到问题,由于你的异常原因不准确不唯一。