前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基础|换个角度看原生Error对象

基础|换个角度看原生Error对象

作者头像
用户1097444
发布2022-06-29 15:48:47
2970
发布2022-06-29 15:48:47
举报
文章被收录于专栏:腾讯IMWeb前端团队

前端爱好者的知识盛宴

这篇推文提供者为我厂的旷旭卿

欢迎留言转发提问

Error 对象在 JS 中貌似是一个长期被忽略的对象,

很多人宁愿用别的方法来描述错误,例如一个特别类型的返回值,或者通过返回码

但其实这个对象从 ES1 里引入开始就带来了无限的可能性。

而笔者开发代码的时候,

一直偏好将函数的正常输出和异常分开,类似这样:

function mustBeEqual(a, b) {

  if (a !== b) {

    throw new Error(`${a} is not equal with ${b}`);

  }

  return true;

}

try {

  const equal = mustBeEqual(1, 1);

  console.log('1 is equal with 1');

  const equal2 = mustBeEqual(1, 2);

  console.log('1 is equal with 2');

} catch (err) {

  console.error(err.message);

  console.error(err.stack);

}

可以看到正确的输出错误的输出泾渭分明:

我一直觉得这样的写法有几个好处

1.可以将异常逻辑主流程逻辑分开,从而使可读性更加清晰。

2.Erlang 中有一句话叫做:“Let it crash”。 - 这不是说让程序真的崩溃了,而是提醒开发者小心处理每一个错误,有的时候崩溃了会更加容易发现问题所在。

3.Error 对象的一些属性,例如 stack 对于发现问题所在位置其实非常有帮助,它对于还原问题帮助非常大。

Error 对象的具体参数请参考一下 MDN,就此不再多述: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Error

我只想说 Error 的主要用法。

继承出业务错误类型

在项目开发中,会碰到各种各样的网络、数据库、外部 RPC 调用,各种问题出现之后难以以一种统一的方案去解决。

此时其实可以通过继承几个业务错误,把底层错误转换为自己项目中所使用的,二次抛出后进行处理。

例如下面代码,摘取自 http://git.code.oa.com/weplus/serverplus/blob/master/src/errors.ts 这里是 Typescript 的语法。

/**

 * Base Error

 */

class BaseError extends Error {

  public status: number;

  public code?: number;

}

/**

 * Request not passed validation will trigger

 */

class BadRequestError extends BaseError {

  public status = 400;

}

/**

 * The user was not login

 */

class UnauthorizedError extends BaseError {

  public status = 401;

}

export {

  BadRequestError,

  UnauthorizedError,

}

这样做有几个好处:

上层可以对输出进行统一处理

底层输出只有两种正常返回和异常,所有的异常都是一个错误的对象,这样就可以简化处理逻辑,对正常输出走业务逻辑,而错误会全部进入 catch 段进行异常处理。

在上面的例子中,HTTP 状态码就是依靠错误的 status 属性进行确定,当某个业务流程需要返回一个错误时,直接 throw 即可。

摘自 

http://git.code.oa.com/weplus/serverplus/blob/master/src/base-model-controller.ts

/**

  * Get single record method

  */

public async get(req, res) {

  let authenticated;

  try {

    authenticated = await this.apiAuthenticate(req);

  } catch (err) {

    throw err;

  }

  if (typeof authenticated === 'boolean' && !authenticated) {

    throw new this.errors.ForbiddenError('Permission denied', {

      requestId: req.headers['X-Request-Id'],

    });

  }

  const id = req.params.id;

  const result = await this.__get(req, id, req.query, req.params);

  return result;

}

上面的路由层接收到一个错误,而不是一个正常的返回值时,就会将它作为错误进行输出

通过继承链,找到具体类型和原因。

通过 instanceof 去找错误,效率比通过字符串高出数倍不止,可以将程序内的错误,和给用户的提示分开,可以根据不同的错误类型,进行不同的处理

const err = new TypeError('Something went wrong');

err instanceof TypeError

// true

err instanceof Error

// true

err instanceof RangeError

// false

err.message

// "Something went wrong"

异常本身是可被触发的,可以干更多事情

例如将错误自己上报给错误监测系统。

在 http://git.code.oa.com/weplus/serverplus/blob/master/src/errors.ts 还可以看到,在基类错误构建函数中,还有一段 Raven.captureException() 的上报方法,这样我们在这里利用了错误的构建时,将自身连带附加信息一起上报给了 Sentry(Sentry 是什么情参考《Web 前后端错误上报及问题跟踪》)。

/**

 * Base Error

 */

class BaseError extends Error {

  public status: number;

  public code?: number;

  constructor (message, context: any = {}) {

    super(message);

    Object.setPrototypeOf(this, BaseError.prototype);

    if (!config.sentry) {

      return this;

    }

    const extra = {

      status: this.status,

      code: this.code,

      ...context,

    };

    Raven.captureException(this, {

      extra,

    });

  }

}

这样一来,当服务器发生异常时候,不用改动主逻辑代码,自动实现了错误上报,这样可以作为一个单独的错误上报层在项目中使用,而且它在上报的时候,还能带上上下文,这样对于错误还原帮助巨大。

还是上面那个例子,这里在非法请求时在返回 Permission Denied 的同时,把前端请求过来的 requestId 一起附带上报,前端整合 Sentry 后可以做全链路的错误还原。

  if (typeof authenticated === 'boolean' && !authenticated) {

    throw new this.errors.ForbiddenError('Permission denied', {

      requestId: req.headers['X-Request-Id'],

    });

  }

面向错误进行应用开发

这种开发模式可以根据场景使用,需要将之前的思维进行小幅度调整,之前 res.code === 0 && doSomethingRight() || doSomethingWrong() 的方式得用 try catch 重新进行梳理,但这样做可以让主线流程代码变得很干净,减少大量的 if else。

面向错误进行开发,需要控制好 try catch 的颗粒度,理论上都是越细越好的,如果一个大的 try 都裹在一起,任何一处发生问题后都会走入 catch 环节这会加大判断错误问题发生位置的难度,尤其是在某些未对底层错误进行二次捕获抛出的架构中会更加严重。

过去和未来

在早期的浏览器引擎中, try catch 方式是比较低效无法被优化的,不过现在新版的 V8 引擎 TurboFan 已经对 try catch 进行了大幅度调整,之前无法被优化的代码也可以以最优方式运行,而服务器端截止本文完稿,搭载 TurboFan 的 Node 8 已经进入 Stable,预期 10 月份进入 LTS,这会让 try catch 的使用更加放心。

面向错误进行开发这种开发模式其实在 Java、Python 或其它语言中已经非常普遍,但在 Javascript 领域目前感觉比较好的是 NodeJS 上的 ORM 库 Sequelize,它里面对错误都进行了良好封装。

我希望未来这样比较小,但是有用的开发模式能更加普及。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-12-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯IMWeb前端团队 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档