【人人都能读标准】17. 底层算法:ECMAScript的错误处理机制

本文为《人人都能读标准》—— ECMAScript篇的第17篇。我在这个仓库中系统地介绍了标准的阅读规则以及使用方式,并深入剖析了标准对JavaScript核心原理的描述。


我们在11.程序完整执行过程说过,一个程序的运行会经历三个阶段:初始化Realm环境 - 解析脚本 - 执行脚本。

在解析脚本的过程中,会对语法解析得到的解析树,进行先验错误的检查。如果此时发现语法不合法,就会提前终止整个程序的执行。这一阶段发生的错误称为解析错误(parsing errors)

在执行脚本的过程中,当一个语句的执行结果是类型为throw的完成记录器(以下均称“throw完成”)时,表示这个语句出现了某个方面的错误,这一个阶段发生的错误称为运行时错误(runtime errors) 。此时有两种情况:一种是错误被捕获(如使用try/catch语句),此时程序的执行会跳转到对应的错误处理逻辑上;另一种是错误没有被捕获,则程序终止执行。

本节我会先从ECMAScript中的Error对象讲起,然后我会以try/catch语句为主要例子,为你展示先验错误的检查过程以及运行时错误的捕获过程。最后,我会从标准的角度分析为何异步代码的错误往往无法被正常捕获,并基于这个原因提供一些解决方案。


Error对象

我们可以从原型链的视角,看到所有类型的Error构造器:

error-prototype-chain

不同类型的Error构造器,结构几乎都是一样的。区别只在于:构造器的名字、prototype对象的 name属性与message属性。AggregateError的实例对象还有一个额外的errors属性。

我用一张表为你总结所有Error类型的含义以及触发示例:

错误类型含义触发示例
EvalError一个遗留(legacy)的错误类型,现已不会由引擎抛出,只是为了兼容性保留。
RangeError表示值超出了安全的范围。1 .toFixed(-1)
RefereneceError表示不合法的引用。console.log(a);let a
SyntaxError表示语法错误。const a;
URIError全局URI解析函数抛出的错误。decodeURIComponent('%')
AggregateError多个错误需要包装在一个错误中。Promise.any([Promise.reject(1), Promise.reject(2)])
TypeError当其他的原生错误类型都不适用时,就抛出该类型的错误。var a; a()

解析错误

解析错误是那些可以在代码执行前就发现的错误,解析错误会以SyntaxError表示。

在6.算法中,我以词法声明语句的先验错误作为例子,展示了解析阶段检查语法错误的过程。在这里,我们再举一个try/catch语句的先验错误作为例子:

try-catch-error

在解析catch块的过程,会在三个地方检查错误:

  • 如果catch使用了相同名字的参数,就抛出SyntaxError;

    try{}catch(e, e){}
    
  • 如果catch块中存在某个词法声明变量,其变量名与catch参数名重复,就抛出SyntaxError;

    try{}catch(e){let e}
    
  • 如果catch块中存在某个变量声明的变量,其变量名与catch参数名有重复,且catch参数不是以BindingIdentifier的形式出现的,就抛出SyntaxError:

    try {}catch({name}){var name = 10}
    

实际上,除了解析全局代码,还有两种情况也会扫描先验错误:第一种是引入模块的时候,第二种是使用eval() 函数的时候。与全局代码一样,在解析模块代码时,如果发现错误,模块就不会被初始化。在解析eval()的参数时,如果发现错误,eval就不会执行。


运行时错误

我们在7.规范类型讲过,在执行语句列表的时候,当某个语句的执行结果为硬性完成(类型为breakcontinuereturnthrow的完成记录器),会提前终止语句列表的执行,并以这个硬性完成作为语句列表的执行结果,交由外部代码作处理。

对于返回“throw完成”的语句列表,可以使用try/catch语句对该“throw完成”进行处理。

try语句主要有三种形态:

  • try - catch
  • try - catch - finally
  • try - finally
try { 
    throw "something wrong"
} catch(e) { 
    console.log('caught:' + e)
} finally { 
    console.log('do some clean up job')
}

// caught:something wrong
// do some clean up job

我们可以从try - catch - finally语句的求值语义了解到它的执行过程:

try-catch-evaluation

把这段逻辑翻译过来大致如下:

  1. 先执行try块中的代码,try块的执行与普通代码块的执行没有区别,都是依次执行块内语句列表中的语句。

  2. 如果try块执行的结果是一个throw完成,那么执行catch块。catch块与普通代码块的执行过程也基本一样,唯一的区别是:它会把catch(err){} 语句中的err参数,以词法变量的形式实例化,并以throw完成的[[Value]]字段的值初始化err的值。

  3. 执行finally块。

  4. 如果finally块是正常完成,返回catch块的完成记录器,如果catch块没有被执行过,则返回try块的完成记录器。

从这里的算法你会发现,不管try块、catch块的执行结果是什么,finally块都一定会执行。因此,你可以看到以下神奇的行为:

  • 在try块中使用continue:

    for (let i = 0; i < 10; i++) {
        try {
            continue
        } finally {
            console.log(i)
        }
    }
    
    // 依旧输出0~9
    
  • 在catch块中使用return语句:

    (function(){
      try {
        throw new Error
      } catch (err) {
        return err
      } finally {
        console.log("You can't return without executing me")
      }
    })()
    // 输出"You can't return without executing me"
    
  • 即便catch块抛出了错误,也必须等finally执行完再抛:

    try {
      throw new Error
    } catch {
      throw new Error
    } finally {
      console.log("You can't throw without executing me")
    }
    // 输出"You can't throw without executing me"
    // ❌:Uncaught Error
    

异步代码的错误捕获

有的时候,异步代码的错误无法被正常捕获,比如像下面这样:

const throwFn = () => {throw new Error("catch me please")}

try {
  setTimeout(throwFn, 0)
} catch(e){
  console.log(e)
}
// ❌ Uncaught Error:catch me please

很多人会从调用栈的角度去解释它的原因,但从标准的角度看,根本原因是:此时try块的执行结果是一个正常完成!

setTimeout(throwFn, 0)所做的事情,就是注册一个宏任务,并在0秒之后执行。所以它本身的执行结果是一个正常完成,由此导致了整个try块都是正常完成。而真正抛出错误的,是后续宏任务的实际执行。

从这个角度看,为了使得异步代码的错误得到捕获,我们必须让异步代码作为try块内语句列表的一部分执行。因此,我们可以这么修改代码:

setTimeout(() => {
  try {
    throwFn()
  }catch(e){
    console.log(e)
  }
}, 0)

在ECMAScript中,除了try/catch,其实还有另一个API也可以捕获错误 —— Promise。Promise神奇的一点在于:在Promise内执行的代码所产生的一切错误,都不会终止程序的执行。比如下面这样:

new Promise(() => {throw new Error})
console.log("1") // 语句会执行,输出1

new Promise(() => {throw new Error}).catch(()=>{throw new Error})
console.log("2") // 语句会执行,输出2

我们可以从Promise的构造器方法看到其原因:

promise-constructor

从框出的部分你可以看到,执行executor的时候,如果产生了硬性完成,并不会返回该硬性完成给外部代码,而是Promise通过触发reject方法自己内部消化掉了。 使用Promise.prototype.then以及Promise.prototype.catch注册的代码,也会有同样的错误处理过程。

所以,对于前面的例子,我们还可以这么改:

setTimeout(() => {
    new Promise(throwFn).catch(e => console.log(e))    
}, 0)

如果你对宏任务、微任务执行时机上的细微差异没有那么在意,那么你还可以把“等待定时器执行”与“执行throwFn”两个部分分开:

new Promise((resolve) => setTimeout(resolve, 0))
  .then(() => {throwFn()})
  .catch(e => console.log(e))

这个时候,你就可以使用async函数让代码变得足够优雅:

(async function(){
  try {
    await new Promise((resolve) => setTimeout(resolve, 0))
    throwFn()
  } catch(e){
    console.log(e)
  }
})()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/4290.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MyBatis-面试题

文章目录1.什么是MyBatis?2.#{}和${}的区别是什么&#xff1f;3.MyBatis的一级、二级缓存4.MyBatis的优缺点5.当实体类中的属性名和表中的字段名不一样 &#xff0c;怎么办 &#xff1f;6.模糊查询like语句该怎么写?7.Mybatis是如何进行分页的&#xff1f;分页插件的原理是什…

渗透测试之冰蝎实战

渗透测试之冰蝎实战1.基本使用2.命令执行&虚拟终端3.文件管理4.反弹shell5.内网资产扫描6.内网穿透7.数据库管理“冰蝎”是一款动态二进制加密网站管理客户端 下载地址 1.基本使用 运行冰蝎&#xff0c;打开传输协议&#xff1a; 生成一个php远程马&#xff1a; 点击生成…

【测试基础】之07 linux基础

Linux操作系统Linux操作系统介绍操作系统&#xff1a;管理计算机硬件与软件 资源的计算机程序&#xff0c;同时也是计算机系统的内核与基石。简单地说&#xff0c;操作系统就是出于用户与计算机系统硬件之间用于传递信息的系统程序软件。例如&#xff1a;操作系统会在接收到用户…

金三银四,你准备好面试了吗? (附30w字软件测试面试题总结)

不知不觉&#xff0c;已是3月下旬。最近有很多小伙伴都在跟我谈论春招面试的问题&#xff0c;其实对于面试&#xff0c;我也没有太多的经验&#xff0c;只能默默地把之前整理的软件测试面试题分享给Ta。今天就来大致的梳理一下软件测试的面试体系&#xff08;每一部分最后都有相…

Vue3学习笔记(5.0)

Vue.js循环语句 v-for指令需要以site in sites形式的特殊语法&#xff0c;sites是源数据数组并且site是数组元素迭代的别名。 v-for可以绑定数据到数组来渲染一个列表&#xff1a; <!--* Author: RealRoad1083425287qq.com* Date: 2023-03-26 16:26:51* LastEditors: Mei…

图解redis的client的实现

目录 1.引言 2.客户端属性 2.1套接字描述符 2.2 name 2.3 客户端标志 2.4输入缓冲区 2.5命令与命令参数 2.6命令实现的函数 2.7输出缓冲区 2.8身份验证 2.9 时间 3.客户端的创建的关闭 3.1普通客户端的创建 3.2普通客户端的关闭 3.AOF的伪客户端 1.引言 Redis服务…

(数字图像处理MATLAB+Python)第二章数字图像处理基础-第三、四节:数字图像的生成和数值描述

文章目录一&#xff1a;数字图像的生成与表示&#xff08;1&#xff09;图像信号的数字化&#xff08;2&#xff09;数字图像类型二&#xff1a;数字图像的数值描述&#xff08;1&#xff09;常用坐标系&#xff08;2&#xff09;数字图像的数据结构&#xff08;3&#xff09;常…

Typora使用

Typora Typora 是一款支持实时预览的 Markdown 文本编辑器。 1. 基础操作 1.1标题 # 一级标题## 二级标题### 三级标题#### 四级标题##### 五级标题###### 六级标题1.2 引用 > 引用内容1 > 引用内容2 >> 引用内容31.3 斜体 *斜体* _斜体_1.4 加粗…

mysql整理

文章目录概述SQLDDLDMLDQL单表查询多表查询DQL的执行顺序DCL管理用户控制权限函数约束事务存储引擎索引概述语法性能分析索引的使用SQL的优化insert优化主键优化Order by优化其它优化存储对象视图存储过程基本操作变量IF条件判断参数循环条件处理程序存储函数触发器锁全局锁表级…

Mysql-缓冲池 buffer pool

缓冲池 buffer pool innodb中的数据是以【页】的形式存储在磁盘上的表空间内&#xff0c;但是【磁盘的速度】和【内存】相比简直不值一提&#xff0c;而【内存的速度】和【cpu的速度】同样不可同日而语&#xff0c;对于数据库而言&#xff0c;I/O成本永远是不可忽略的一项成本…

基于Elman神经网络预测计费系统的输出(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 简单循环网络&#xff08;simple recurrent networks&#xff0c;简称SRN&#xff09;又称为Elman network&#xff0c;是由Jeff…

什么是AI文章生成器-AI文章生成器批量生成文章

AI文章生成器有哪些 目前市面上存在一些可以生成文章的 AI 文章生成器&#xff0c;以下是其中几种常见的&#xff1a; OpenAI GPT-3&#xff1a; OpenAI GPT-3 是目前最先进、最著名的 AI 文章生成器之一&#xff0c;它可以生成各种类型的文章&#xff0c;例如新闻报道、科学报…

我的Macbook pro使用体验

刚拿到Mac那一刻&#xff0c;第一眼很惊艳&#xff0c;不经眼前一亮&#xff0c;心想&#xff1a;这是一件艺术品&#xff0c;太好看了吧 而后再体验全新的Macos 系统&#xff0c;身为多年的win用户说实话一时间还是难以接受 1.从未见过的访达&#xff0c;不习惯的右键 2. …

[论文解析] Cones: Concept Neurons in Diffusion Models for Customized Generation

论文连接&#xff1a;https://readpaper.com/pdf-annotate/note?pdfId4731757617890738177&noteId1715361536274443520 源码链接&#xff1a; https://github.com/Johanan528/Cones 文章目录OverviewWhat problem is addressed in the paper?Is it a new problem? If so…

PMP一般要提前多久备考?

PMP很迷&#xff0c;有只备考了一周过的&#xff0c;也有备考几个月过的。保险起见&#xff0c;预留两个月比较靠谱&#xff0c;尤其现在是新考纲&#xff0c;PMP新版大纲加入了 ACP 敏捷管理的内容&#xff0c;而且还不少&#xff0c;敏捷混合题型占到了 50%&#xff0c;前不久…

AcWing3662. 最大上升子序列和(线性DP + 树状数组优化 + 离散化处理)

AcWing3662. 最大上升子序列和&#xff08;线性DP 树状数组优化 离散化处理&#xff09;一、问题二、分析1、DP过程&#xff08;1&#xff09;状态表示&#xff08;2&#xff09;状态转移2、数据结构优化&#xff08;1&#xff09;树状数组维护最值&#xff08;2&#xff09;…

K8s 弃用 Docker!一文介绍 containerd ctr、crictl 使用

containerd 是一个高级容器运行时&#xff0c;又名 容器管理器。简单来说&#xff0c;它是一个守护进程&#xff0c;在单个主机上管理完整的容器生命周期&#xff1a;创建、启动、停止容器、拉取和存储镜像、配置挂载、网络等。 containerd 旨在轻松嵌入到更大的系统中。Docke…

【ASPLOS 2023】图神经网络统一图算子抽象uGrapher,大幅提高计算性能

作者&#xff1a;周杨杰、沈雯婷 开篇 近日&#xff0c;阿里云机器学习平台PAI和上海交通大学冷静文老师团队合作的论文《图神经网络统一图算子抽象uGrapher》被ASPLOS 2023录取。 为了解决当前图神经网络中框架中不同的图算子在不同图数据上静态kernel的性能问题&#xff0…

【前沿技术】文心一言 PK Chat Gpt

目录 写在前面 一、文心一言 二、Chat GPT 三、对比 四、总结 写在前面 随着人工智能技术的不断发展和普及&#xff0c;越来越多的智能应用走入了人们的日常生活&#xff0c;如智能语音助手、智能客服、机器翻译等等。在这些应用中&#xff0c;自然语言生成&#xff08;…

看完不再愁 | 图解TCP 重传、滑动窗口、流量控制、拥塞控制

目录 前言 正文 &#x1f332; 重传机制 1. 超时重传 2. 快速重传 3. SACK 方法 4. Duplicate SACK &#x1f332; 滑动窗口 &#x1f333; 流量控制 &#x1f333; 拥塞控制 1. 慢启动 2. 拥塞避免算法 3. 拥塞发生 4. 快速恢复 前言 前面我们讲到「硬不硬你说…
最新文章