js终止程序,我常用throw 替代 return

js终止程序有两种方式(如果还有别的请告知我)

  1. throw
  2. return

这两个好像是两大阵营,前者我个人最推崇,但是很少见人用, 不知道啥原因(兴许是讨厌写try catch吧)。

刚入门那会,总觉得下面这样的验证好麻烦

js 复制代码
  const formValues = {
    mobile: '',
    name: '',
  }

  function onSubmit() {
    if (!formValues.name) {
      alert('请输入用户名')
      return 
    }

    if (!formValues.mobile) {
      alert('请输入手机号')
      return 
    }
    
    // n个表单验证,return N次 alert N 次
  }

后来发现,可以用throw改进一下

js 复制代码
  const formValues = {
    mobile: '',
    name: '',
  }

  function onSubmit() {
    try {
      if (!formValues.name) throw ('请输入用户名')
      if (!formValues.mobile) throw String('请输入手机号')

    } catch (error) {
      alert(error)
    }
  }

这样就好看多了(但很多人觉得,try catch 难看 😂)。 后来验证多了,就把验证挪到单独一个函数里

js 复制代码
  const formValues = {
    mobile: '',
    name: '',
  }

  function validateFormValues() {
    if (!formValues.name) throw ('请输入用户名')
    if (!formValues.mobile) throw String('请输入手机号')
  }

  function onSubmit() {
    try {
      validateFormValues()

    } catch (error) {
      alert(error)
    }
  }

主函数看起来干净些了。这是throw才能做到的,报错跳出调用栈。

由此引出了之前看过的一种写法,用return(我不喜欢)

js 复制代码
  const formValues = {
    mobile: '',
    name: '',
  }

  function validateFormValues() {
    if (!formValues.name) {
      alert('请输入用户名')
      return 
    }
    if (!formValues.mobile) {
      alert('请输入手机号')
      return 
    }
    
    return true
  }

  function onSubmit() {
    const isValidateFormValuesSuccess = validateFormValues();

    // 这点我不喜欢,因为还要再写一次判断
    if (!isValidateFormValuesSuccess) return 
  }

如果是遇到嵌套深的复杂场景,函数套函数,是不是就很无力了,因为没法跳转函数栈,只能层层判断。

但是throw就可以无视嵌套,直接报错,最晚层接住错误就可以了。当我们写代码的时候,想终止程序,就直接throw。

看下面这段代码 promise async await 联合使用。可用空间就大了撒。 从此随便造,函数大了就拆逻辑成小函数,想终止就throw

js 复制代码
  // promise里面throw 错误 = reject(错误)
  async function onSubmit() {
    try {
      await new Promise((resolve) => {
        throw String('故意报错')
      })
      
      console.log('a'); // 不会执行
    } catch (error) {
      alert(error) // 结果:alert 故意报错
    }
  }

  // promise里面 catch 也可以直接抛错误
  async function onSubmit() {
    try {
      await new Promise((resolve, reject) => {
        reject('故意报错')
      }).catch(error => {
        throw error
      })
      
      console.log('b'); // 不会执行
    } catch (error) {
      alert(error)
    }
  }

可能有的小伙伴会想,try catch 有性能问题。看下图,来源于经典书《高性能javaScript》

之前公司小伙伴也有这个疑问,我翻了书加上用chorme 微信小程序编辑器,去测过,最终差别不大,没问题的,使劲用。

由此启发,这时候引入一个catchError函数,专门用来接收报错

js 复制代码
// 报错白名单,收到这个就不提示报错了,标明是主动行为
const MANUAL_STOP_PROGRAM = "主动终止程序";

/**
* @feat < 捕获错误 >
* @param { unknown } error 错误
* @param { string } location 错误所在位置,标识用的
* @param { Function } onParsedError 解析错误,可能需要把这个错误弄到页面上显示是啥错误
*/
 function catchError(error, location, { onParsedError } = {}) {
  try {
    const errorIsObj = error && typeof error === "object";

    if (errorIsObj) throw JSON.stringify(error);

    // 其他处理,比如判断是取消网络请求,错误集中上报等等,大家自由发挥,有啥好想法欢迎评论区留言

    throw error;
  } catch (error) {
    console.error(`${location || ""}-捕获错误`, error);

    if (new RegExp(MANUAL_STOP_PROGRAM).test(error)) throw MANUAL_STOP_PROGRAM;

    // 错误解析完毕
    onParsedError && onParsedError(error);

    alert(error) // 弹窗提示错误
    throw MANUAL_STOP_PROGRAM;
  }
}

在上面中 MANUAL_STOP_PROGRAM 就是个白名单了,专门用来标识是主动报错,但是不提示错误 每次 catchError 之后,要把 MANUAL_STOP_PROGRAM 继续抛出来,因为我们可能业务调用链很深,需要多个地方使用到 catchError,但是只需要报错一次,而且需要报错告知外层不执行后续逻辑。

再结合 location 参数,我们可以看到清晰的错误来源

catchError 这个是我得初步设想,一直想做统一的错误收集中心。如果您有好的想法,欢迎告知评论区。

上面执行后,控制台是有点难看的

通过window.onerror能收集到部分错误,但是异步的就收集不到了。(async promise 这些就没办法了),如果您有啥办法能收集到,麻烦告知一下。

但是控制台难看有啥关系呢。(反正用户和老板也看不到 😂)

js 复制代码
/**
* @param { string } message
* @param { source } 表示发生错误的脚本文件的 URL
* @param { lineno } 表示发生错误的行号
*/
window.onerror = function(message, source, lineno) {
// 错误处理逻辑
};

下面是一个比较极端的例子,演示一下深层级的报错效果

js 复制代码
  // 生成假数据
  async function genrateMockList() {
    try {
      const list = await Promise.all(
        new Array(100).fill(',').map((item, index) => {
          try {
            if (index === 1) throw String('map 故意报错,嵌套比较深了')

            return {
              index: 'name'
            }
          } catch (error) {
            catchError(error, 'genrateMockList__item')
          }
        })
      )
      return list
    } catch (error) {
      catchError(error, 'genrateMockList')
    }
  }

  async function getDetails() {
    try {
      const dataList = await genrateMockList();
      console.log('a') // 不会执行
    } catch (error) {
      catchError(error, 'getDetails')
    }
  }

  getDetails();

笔者始终认为,写代码很重要的一点是 数据结构 和 程序流控制。

结构清楚了,所有的东西都能一生二二生四一直延伸变化。

程序流控制我们尽量做到简单清晰。

别再纠结用了多少个try catch 多难看了,多一个try catch 就多一分安心,特别是复杂的业务逻辑,可能需要经过5-6个小函数,这时候加上try catch 就能把报错范围缩小,等到代码完全可靠后再移除 try 也不迟。

兴许return 当初设计就只是为了返回值吧,我总觉得throw才是js设计者的终止程序的用意。这段历史有知道的也欢迎说一下。

以上内容供大家参考。有啥看法欢迎评论区留言。

预告:下周开始写一些组件设计思考。是一个系列,存货不多,顶多写几篇

相关推荐
叫我菜菜就好9 分钟前
【Flutter_Web】Flutter编译Web第三篇(网络请求篇):dio如何改造方法,变成web之后数据如何处理
前端·网络·flutter
NoneCoder15 分钟前
CSS系列(26)-- 动画性能优化详解
前端·css·性能优化
滚雪球~15 分钟前
@vue/cli启动异常:ENOENT: no such file or directory, scandir
前端·javascript·vue.js
GDAL26 分钟前
vue3入门教程:ref函数
前端·vue.js·elementui
GISer_Jing34 分钟前
Vue3状态管理——Pinia
前端·javascript·vue.js
好开心331 小时前
axios的使用
开发语言·前端·javascript·前端框架·html
Domain-zhuo1 小时前
Git常用命令
前端·git·gitee·github·gitea·gitcode
菜根Sec1 小时前
XSS跨站脚本攻击漏洞练习
前端·xss
m0_748257182 小时前
Spring Boot FileUpLoad and Interceptor(文件上传和拦截器,Web入门知识)
前端·spring boot·后端
桃园码工2 小时前
15_HTML5 表单属性 --[HTML5 API 学习之旅]
前端·html5·表单属性