不知道你有没有遇到这样一种情况,某天你写的代码在线上突然发生错误,然后你打开控制台,却对着打过包的错误信息毫无头绪?又或者说是代码在node端出现了问题,你查看错误日志的时候,却发现日志文件中都是杂乱的错误堆栈信息。
其实上面这些问题都可以通过在代码中引入合适的错误机制进行解决。大部分时候,由于程序员在开发过程中更加关注需求的实现,反而会忽视一些底层的工作。而错误处理机制就相当于我们代码上的最后一道保险,在程序发生已知或者意外的问题的时候,可以让开发者在第一时间获取信息,从而快速定位并解决问题。
常用的错误处理机制
首先我们来了解一下目前前端领域到底有哪些错误处理机制。
try catch
try...catch这种错误处理机制一定是大家最熟悉的,Javascript语言内置的错误处理机制可以在检测到代码异常的时候直接进行捕获并处理。
function test() { try { throw new Error("error"); } catch(err) { console.log("some error happened:"); } } test()
node原生错误处理机制
大多数Node.js核心API都提供的是利用回调函数处理错误,例如:
const fs = require('fs'); function read() { fs.readFile("/some/file/does-not-exist", (err, data) => { if(err) { throw new Error("file not exist"); } console.log(data); }); } read();
通过回调函数的err参数来检查是否出现错误,再进行处理。之所以Node.js采用这种错误处理机制,是因为异步方法所产生的方法并不能简单地通过try...catch机制进行拦截。
promise
Promise是用于处理异步调用的规范,而其提供的错误处理机制,是通过catch方法进行捕获。
fs.mkdir("./temp").then(() => { fs.writeFile("./temp/foobar.txt", "hello"); }).catch(err => { console.log(err) });
async/await + try catch
第三种错误处理机制是采用async/await语法糖加上try...catch语句进行的。这样做的好处是异步和同步调用都能够使用统一的方式进行处理了。
async function one() { await two(); } async function two() { await "hello"; throw new Error("error"); } async function test() { try { await one(); } catch(error) { console.log(error); } } test();
解决方案
promisify
如果你的代码中充斥着多种不同的错误处理模式,那么维护起来是十分困难的。而且代码的可读性也会大大降低。因此,这里推荐采用的统一的解决方案。对于同步代码来说,直接使用try...catch方式进行捕获处理即可,而对于异步代码来说,建议转换成Promise然后采用async/await + try...catch这种方式进行处理。这样风格统一,程序的健壮性也大大加强。例如下面这个数据库请求的代码:
const database = require("database"); function promiseGet(query) { return new Promise((resolve, reject) => { database.get(query, (err, result) => { if (err) { reject(err); } else { resolve(result); } }) }) } async function main() { await promiseGet("foo"); } main();
自定义错误类型
直接使用系统原生的错误信息通常会显得太过单薄,不利于后续进一步的分析和处理。所以为了让代码的错误处理机制的功能更加强大,我们势必要多花点精力进行额外的改造。
可以通过扩展基础的Error类型来达到这一目的。
一般来说,要根据错误发生的位置采用不同的错误类型。
首先是应用层错误,它会保存额外的线索数据:
class ApplicationError extends Error { constructor(message, options = {}) { assert(typeof message === 'string'); assert(typeof options === 'object'); assert(options !== null); super(message); // Attach relevant information to the error instance // (e.g., the username). for (const [key, value] of Object.entries(options)) { this[key] = value; } } get name() { return this.constructor.name; } }
接着,可以再定义用户接口的错误类型,该类型主要用于直接返回给客户端,比如错误状态码等等。
class UserFacingError extends ApplicationError { constructor(message, options = {}) { super(message, options); } } class BadRequestError extends UserFacingError { get statusCode() { return 400 } } class NotFoundError extends UserFacingError { get statusCode() { return 404 } }
另外,对于底层的数据库错误来说,可能需要更加细节的错误信息。此时也可以根据业务需要进行自定义:
class DatabaseError extends ApplicationError { get toString() { return "Errored happend in query: " + this.query + "\n" + this.message; } } // 使用的话 throw new DatabaseError("Other message", { query: query });
化繁为简,集中处理
express
有了基础的错误数据类型后,我们可以在代码里针对不同的错误类型采取不同的解决方案。 接下来,以Express应用为例讲解一下使用方法。
app.use('/user', (req, res, next) => { const data = await database.getData(req.params.userId); if (!data) { throw new NotFoundError("User not found") } // do other thing }); // 错误处理中间件 app.use(async (err, req, res, next) => { if (err instanceof UserFacingError) { res.sendStatus(err.statusCode); // or res.status(err.statusCode).send(err.errorCode) } else { res.sendStatus(500) } // 记录日志 await logger.logError(err, 'parameter:', req.params, 'User Data:', req.user); // 发送邮件 await sendMailToAdminIfCritical(); })
具体到实际场景中,需要在不同的路由中抛出不同的错误类型,然后我们就可以通过在错误处理中间件中进行统一的处理。比如根据不同的错误类型返回不同的错误码。还可以进行记录日志,发送邮件等操作。
database
数据库发生错误的时候,除了常规的抛出错误,有时候你可能还需要进行额外的重试或回退操作,如:
// 发生网络错误的时候隔200ms,重试3次 function query(queryStr, token, repeatTime = 0, delay = 200) { try { await db.query(queryStr); } catch (err) { if (err instanceof NetworkError && repeatTime < 3) { query(queryStr, token, repeatTime + 1, delay); } throw err; } }
未处理错误
对于未处理的错误来说,我们可以使用node.js的unhandledRejection事件进行监听:
process.on('unhandledRejection', error => { console.error('unhandledRejection', error); // To exit with a 'failure' code process.exit(1); });
而且从Node.js 12.0开始,可以使用以下命令启动程序:
node app.js --unhandled-rejections
这样也能够在发现未处理异常的时候进行处理,官方支持了三种对应的处理模式:
strict: Raise the unhandled rejection as an uncaught exception.
warn: Always trigger a warning, no matter if the unhandledRejection hook is set or not but do not print the deprecation warning.
none: Silence all warnings.
总结
最后,总结一下。为了实现可扩展和可维护的错误处理机制,我们可以需要注意以下几个方面:
- 使用自定义Error类,后续还能根据业务需要进行扩展
- 将异步代码转换成Promise,然后统一使用async/await + try...catch的形式进行错误捕获
- 尽量采用统一的错误处理中间件函数
- 保持Error信息可理解,返回合适的错误状态和代码
- 对于未处理的错误,要即使捕获并记录
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 凤飞飞《我们的主题曲》飞跃制作[正版原抓WAV+CUE]
- 刘嘉亮《亮情歌2》[WAV+CUE][1G]
- 红馆40·谭咏麟《歌者恋歌浓情30年演唱会》3CD[低速原抓WAV+CUE][1.8G]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[320K/MP3][193.25MB]
- 【轻音乐】曼托凡尼乐团《精选辑》2CD.1998[FLAC+CUE整轨]
- 邝美云《心中有爱》1989年香港DMIJP版1MTO东芝首版[WAV+CUE]
- 群星《情叹-发烧女声DSD》天籁女声发烧碟[WAV+CUE]
- 刘纬武《睡眠宝宝竖琴童谣 吉卜力工作室 白噪音安抚》[FLAC/分轨][748.03MB]
- 理想混蛋《Origin Sessions》[320K/MP3][37.47MB]
- 公馆青少年《我其实一点都不酷》[320K/MP3][78.78MB]
- 群星《情叹-发烧男声DSD》最值得珍藏的完美男声[WAV+CUE]
- 群星《国韵飘香·贵妃醉酒HQCD黑胶王》2CD[WAV]
- 卫兰《DAUGHTER》【低速原抓WAV+CUE】
- 公馆青少年《我其实一点都不酷》[FLAC/分轨][398.22MB]
- ZWEI《迟暮的花 (Explicit)》[320K/MP3][57.16MB]