js 捕获异常获取异常信息
As recent versions of JavaScript (formally EMCAScript) provide more programmatic facilities (and syntactic sugar) for us programmers to utilize, code in JavaScript has never been more expressive, though new pitfalls come along. With the advancement of tools in the ecosystem, we are enabled to avoid many of these problems, and write cleaner and clearer code. We have ESLint and its family of plugins to enforce consistent coding standard, and find common problems with static analysis; TypeScript, Flow, and other static type checkers to ensure type safety; Babel to transpile modern and cutting-edge ES syntaxes into syntax compatible with your target.
随着最新版本JavaScript(以前称为EMCAScript)为我们的程序员提供了更多的编程功能(和语法糖),JavaScript中的代码从未像现在这样具有表现力,尽管有新的陷阱。 随着生态系统中工具的进步,我们能够避免许多此类问题,并编写更清晰的代码。 我们拥有ESLint及其插件家族来执行一致的编码标准,并发现静态分析中的常见问题; TypeScript , Flow和其他静态类型检查器,以确保类型安全; Babel将现代和前沿的ES语法转换为与目标兼容的语法。
However, there is a “thing” that plagues us from the beginning of time¹, and even with the tools we have today, still needs to be manually catered: exception.
但是,有一种“东西”从一开始就困扰着我们,即使使用我们今天拥有的工具,仍然需要手动处理:“ 例外” 。
Ordinary functions may throw an exceptions. Class constructors may throw exceptions. Class methods may throw exceptions. Generators may throw exceptions. Await
ing rejected Promise
s would results in exceptions thrown. Almost everything may throw an exception. Static analysis doesn't help much in this case, for tracing all possible places that would throw an exception may get every line of your codebase flagged. TypeScript doesn't help too, as exception specification is not a part of a function declaration.
普通函数可能会引发异常。 类构造函数可能会引发异常。 类方法可能会引发异常。 生成器可能会引发异常。 Await
ING拒绝 Promise
s就导致抛出异常。 几乎所有事物都可能引发异常。 在这种情况下,静态分析没有太大帮助,因为跟踪所有可能引发异常的位置可能会标记代码库的每一行。 TypeScript也无济于事,因为异常规范不是函数声明的一部分。
What can we do then?
那我们该怎么办?
异常点 (Point of exception)
Before we delve into more details, let’s play a good old game of “spotting the exception”.
在深入研究更多细节之前,让我们玩一个很好的老游戏“发现异常”。
Can you spot where an exception may be thrown in the following TypeScript files? Let’s consider Car
is a black-boxed class.
您能发现以下TypeScript文件中可能引发异常的地方吗? 让我们考虑一下Car
是一个黑盒类。
介绍异常安全性的概念 (Introduce the concept of exception safety)
Exception safety², a concept commonly associated with C++, is a set of properties that indicate what would happen when an exception arises in a function, and let user of the function reason about how to handle the exception properly.
异常安全性²(通常与C ++关联)是一组属性,它们指示函数中出现异常时将发生的情况,并让函数的用户说明如何正确处理异常。
Generally speaking, exception safety guarantees are divided into 4 levels, in ascending level of safety:
一般来说,异常安全保证按照安全性的提升分为四个级别:
There is no guarantees what would happen when an exception is thrown. There may be (incomplete) side-effects due to interrupted execution, and you cannot be sure whether the program/internal state is coherent.
无法保证引发异常时会发生什么。 由于执行中断,可能会产生(不完全的)副作用,并且您无法确定程序/内部状态是否一致。
There may be side-effects due to partial execution, however, all invariants are preserved, and there is no resource leak. Some program/internal states may be different from that of before the execution, but the all the values are valid.
由于部分执行可能会产生副作用,但是,所有不变量均被保留,并且没有资源泄漏。 某些程序/内部状态可能与执行前的状态不同,但是所有值均有效。
It is guaranteed to have no side-effect if an exception is thrown. All program/internal states are intact in such case. In other words, the execution either fully completes, or fails as if it is not invoked at all. Similar to an atomic database transaction.
如果抛出异常,则保证没有副作用。 在这种情况下,所有程序/内部状态均保持不变。 换句话说,执行要么完全完成,要么失败,就好像根本没有调用它一样。 类似于原子数据库事务。
It is guaranteed that the function would always succeed, and never throw any exception.
确保该函数将始终成功,并且永远不会引发任何异常。
No exception safety is considered a bug in a program, as it implies the program is incorrect if an exception got thrown.
没有异常安全被认为是程序中的错误,因为这意味着如果引发异常,则程序是不正确的。
That’s it; there is no lower level. A failure to meet at least the basic guarantee is always a program bug. Correct programs meet at least the basic guarantee for all functions. [Sutter04]
而已; 没有更低的水平。 至少不能满足基本保证始终是程序错误。 正确的程序至少满足所有功能的基本保证。 [Sutter04]
“这不只是意味着将所有代码包装在try-catch
块中吗?” (“Doesn’t that just mean wrapping all code in try-catch
block?”)
Unfortunately, no. The program flow is still interrupted within a try-catch
block when an exception is thrown. You may have prevented an exception from escaping your function, however this by itself still doesn't mean anything meaningful in exception safety. The main point is, as mentioned above, to provide a guarantee that what would happen when an exception is thrown.
抱歉不行。 当引发异常时,程序流仍在try-catch
块中中断。 您可能已经阻止了异常转义功能,但是,这本身并不意味着异常安全性有意义。 如上所述,要点是要保证抛出异常时会发生什么。
For example, using a function-wide try-catch
block may make your function "never throw any exception", but you have to guarantee the function "would always succeed" too to be qualify for "no-throw guarantee".
例如,使用功能范围内的try-catch
块可能会使您的函数“从不抛出任何异常”,但是您必须保证该函数“将永远成功”才能获得“无抛出保证”。
This implies that achieving exception safety is never simply a matter of sprinkling try-catch
blocks (or catch
handler for promises), but something that is part of the design.
这意味着实现异常安全绝不是简单地添加try-catch
块(或promise的catch
处理程序),而是设计中的一部分。
Never make exception safety an afterthought. Exception safety affects a class’s design. It is never “just an implementation detail.” [Sutter99]
绝对不要事后考虑例外安全。 异常安全会影响类的设计。 它绝不是“仅仅是实现细节”。 [Sutter99]
在哪里抛出异常,什么时候不安全? (Where are exceptions thrown and when is it unsafe?)
Let’s go back to inspect the code snippets function by function, and see where the exceptions are thrown.
让我们回过头来逐个检查代码片段,并查看在何处抛出异常。
carHelpers.ts
(carHelpers.ts
)
await fetch(...)
on L3 may throw, for example when there is a network error, or when the condition on L5 is true. Does this break exception safety? No, because no internal state is modified, even if an exception is thrown.
例如,当发生网络错误或L5的条件为true时,L3的await fetch(...)
可能会抛出。 这会破坏异常安全吗? 不可以,因为即使抛出异常也不会修改内部状态。
new Car(...)
on L13 may throw. Does this break exception safety? No, because again no internal state is modified even if an exception is thrown. carsCache
is reassigned only when rawCars.map(...)
completes without throwing an exception.
L13上的new Car(...)
可能会抛出。 这会破坏异常安全吗? 不,因为即使抛出异常,也不会修改内部状态。 carsCache
时才重新分配rawCars.map(...)
未抛出异常完成。
Is the function exception safe? Yes, the function has “strong exception safety guarantee”.
该功能异常安全吗? 是的,该功能具有“强大的异常安全保证”。
await getCars()
on L6 may throw, just as we illustrated above. Does this break exception safety? No. No internal state is changed by this point, and getCars
offers strong exception safety guarantee.
正如上面所示,在L6上的await getCars()
可能会抛出。 这会破坏异常安全吗? 不会。此时,内部状态不会改变,并且getCars
提供强大的异常安全保证。
car.plateNumber
on L10 may throw. It may be a surprise to you, but car.plateNumber
may be implemented as a getter function, which may throw an exception. Does this break exception safety? Yes. carByPlateNumberMap
would be partially modified if an exception is thrown in the middle of the loop.
L10上的car.plateNumber
可能会抛出。 这可能会让您感到意外,但是car.plateNumber
可能被实现为getter函数,这可能会引发异常。 这会破坏异常安全吗? 是。 如果在循环的中间抛出异常,则会部分修改carByPlateNumberMap
。
Is the function exception safe? No, internal states would be incoherent when an exception is thrown on L8. This is an example where having a try-catch
block in place still doesn't make a function exception safe.
该功能异常安全吗? 不,当L8抛出异常时,内部状态将是不一致的。 这是一个示例,其中仍然存在try-catch
块仍不能使函数异常安全。
Is the function exception safe? Yes, as no exception is thrown in this function. This function is said to provide “no-throw guarantee”.
该功能异常安全吗? 是的,因为此函数不会引发任何异常。 据说此功能提供“无掷保证”。
await getCarByPlateNumberMap()
may throw on L4, as illustrated above. Does this break exception safety? Yes, for getCarByPlateNumberMap
not being exception safe. If we assume for a moment that getCarByPlateNumberMap
is exception safe, then this function is safe too, as no internal state is modified in this function itself.
await getCarByPlateNumberMap()
可能会在L4上抛出,如上所示。 这会破坏异常安全吗? 是的,因为getCarByPlateNumberMap
不是异常安全的。 如果我们暂时假设getCarByPlateNumberMap
是异常安全的,那么此函数也是安全的,因为此函数本身未修改任何内部状态。
Is the function exception safe? No. If getCarByPlateNumberMap
is exception safe, this function would be exception neutral: thrown exceptions propagates up to the caller verbatim, and the internal states are intact should that happens.
该功能异常安全吗? 否。如果getCarByPlateNumberMap
是异常安全的,则此函数将是异常中立的:抛出的异常将逐字传播到调用方,并且在发生这种情况时内部状态将保持完整。
Is the function exception safe? Yes, and it provides “no-throw exception guarantee”.
该功能异常安全吗? 是的,它提供了“无抛出异常保证”。
索引 (index.ts)
await getCarByPlateNumber(...)
may throw on L7. Does this break exception safety? Yes, as getCarByPlateNumber
is not exception safe.
await getCarByPlateNumber(...)
可能会在L7上抛出。 这会破坏异常安全吗? 是的,因为getCarByPlateNumber
并非异常安全。
Assignment to car.owner
may throw on L8, as car.owner
may have a setter function which may throw an exception. Does this break exception safety? No. Internal states are intact.
分配给car.owner
可能会引发L8,因为car.owner
可能具有setter函数,这可能会引发异常。 这会破坏异常安全吗? 否。内部状态不变。
Assignment to car.plateNumber
may throw on L9, also due to potential setter implementation. Does this break exception safety? Yes and no. Nonetheless, car.owner
is modified at this point. If the internal state of car
is still consistent (no invariant is broken), we may consider this program has "basic exception safety guarantee", otherwise, this is exception unsafe.
同样由于潜在的setter实现,分配给car.plateNumber
可能会在L9上进行。 这会破坏异常安全吗? 是的,没有。 尽管如此, car.owner
修改。 如果car
的内部状态仍然一致(没有不变性被破坏),我们可以认为该程序具有“基本异常安全保证”,否则,这就是异常不安全。
异常安全技术 (Techniques for exception safety)
缩小职能职责 (Narrow down the responsibilities of a function)
A function having multiple distinct responsibilities is hard to write correctly. Consider breaking such a function into multiple functions, spreading the responsibilities.
具有多个不同职责的功能很难正确编写。 考虑将此类功能分解为多个功能,以分散职责。
Remember that “error-unsafe” and “poor design” go hand in hand: If it is difficult to make a piece of code satisfy even the basic guarantee, that almost always is a signal of its poor design. For example, a function having multiple unrelated responsibilities is difficult to make error-safe. [Sutter04]
请记住,“错误不安全”和“设计不良”是并驾齐驱的:如果很难使一段代码甚至不能满足基本保证,则几乎总是表明其设计不佳。 例如,具有多个无关职责的功能很难使错误安全。 [Sutter04]
“克隆并分配” (“Clone-and-assign”)
Clone the object/value/program state you are going to mutate (or create a new instance), operate on the copy, and replace the old value.
克隆要突变的对象/值/程序状态(或创建新实例),对副本进行操作,然后替换旧值。
Let’s take getCarByPlateNumberMap
as an example:
让我们以getCarByPlateNumberMap
为例:
充分利用内置的非变异功能 (Make good use of the built-in non-mutating functions)
ES5 provides a number of non-mutating functions, such as Array.prototype.filter
, Array.prototype.map
, and Array.prototype.reduce
⁴. Array.prototype.slice
was provided back from ES3⁵. These functions return a new instance instead of modifying the original object.
ES5提供了许多非变异函数,例如Array.prototype.filter
, Array.prototype.map
和Array.prototype.reduce
。 Array.prototype.slice
是从ES3⁵返回的。 这些函数返回一个新实例,而不是修改原始对象。
Again let’s take getCarByPlateNumberMap
as an example. With Array.prototype.map
, we can eliminate the for-of-loop
too:
再次以getCarByPlateNumberMap
为例。 使用Array.prototype.map
,我们也可以消除for-of-loop
:
Both implementations would provide “strong exception safety guarantee”.
两种实现方式都将提供“强大的异常安全保证”。
功能编程库/不变数据结构库 (Functional programming library / immutable data structure library)
Data immutability is a core concept of functional programming. There are libraries in JS that provides functional programming-like experience, for example, Bacon.js, fp-ts (TypeScript only), and Ramda. For immutable data structures, Immutable.js, and Immer are two popular libraries providing the facilities.
数据不变性是功能编程的核心概念。 JS中有一些库可提供类似于编程的功能,例如Bacon.js , fp-ts (仅TypeScript)和Ramda 。 对于不可变数据结构, Immutable.js和Immer是两个提供该功能的流行库。
提供最强大的安全保证,不会对不需要的呼叫者造成不利影响[Sutter04] (Give the strongest safety guarantee that won’t penalize callers who don’t need it [Sutter04])
It is not a must to always provide strong safety guarantee. Sometimes it is even impossible to do so.
始终提供强大的安全保证不是必须的。 有时甚至不可能这样做。
When providing strong safety guarantee is difficult, or it would impose unwanted overhead to those who do not need it, consider providing basic safety guarantee. Basic safety guarantee is the least you should always provide.
当提供强大的安全保证很困难,或者会给不需要的人带来不必要的开销时,请考虑提供基本的安全保证。 基本的安全保证是您应始终提供的最低要求。
结论 (Conclusion)
Exception is a thing we cannot avoid when programming in JavaScript (and other languages). Handling exceptions correctly and being exception-concious (knows where the exceptions may be thrown) are essential to write correct code. The concept exception safety helps you, and the users of the code you write reason about the implication of an exception. Always aim for strongest safety guarantee that won’t penalize callers who don’t need it, while always provide at least the basic guarantee.
使用JavaScript(和其他语言)进行编程时,我们无法避免例外。 正确处理异常并具有异常意识(知道异常可能在何处引发)对于编写正确的代码至关重要。 概念异常安全性可以为您提供帮助,您编写代码的用户也可以理解异常的含义。 始终以最强的安全保证为目标,不会对不需要它的呼叫者造成不利影响,同时始终至少提供基本保证。
Thank you very much for reading.
非常感谢您的阅读。
[1]: Precisely from EMCAScript 3rd edition, released in December 1999 and came into public use with the release of Internet Explorer 5.5 in July 2020, in which throw
and try...catch
were introduced into the language. If we count "runtime errors" generated by built-in functions³ that cannot be handled, it would be probably from the very first version of JavaScript.
[1]:准确地取自于1999年12月发布的EMCAScript 3rd版本,并于2020年7月发布的Internet Explorer 5.5正式投入使用,该语言中引入了throw
和try...catch
。 如果我们计算无法处理的内置函数³产生的“运行时错误”,则可能来自JavaScript的第一个版本。
[2]: First introduced by David Arbrahams in Exception Safety in STLport in 1996, and expanded in Lessons Learned from Specifying Exception-Safety for the C++ Standard Library.
[2]:由David Arbrahams于1996年在STLport的“异常安全性”中首次引入,并扩展了“ 从为C ++标准库指定异常安全性中学到的教训” 。
[3]: Section 5.2, P.8, EMCA-262 1st edition
[3]: EMCA-262第1版第5.2节,第8页
[4]: Section 15.4.4.19–15.4.4.21, P.135–138, EMCA-262 5th edition
[4]: 第15.4.4.19–15.4.4.21节,第135–138页,EMCA-262第5版
[Sutter99]: Herb Sutter, Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions. Addison-Wesley, 1999.
[Sutter99]:Herb Sutter, 出色的C ++:47个工程难题,编程问题和解决方案 。 Addison-Wesley,1999年。
[Sutter04]: Herb Sutter and Andrei Alexandrescu, C++ Coding Standards: 101 Rules, Guidelines, and Best Practices. Addison-Wesley, 2004.
[Sutter04]:Herb Sutter和Andrei Alexandrescu, C ++编码标准:101条规则,指南和最佳实践 。 Addison-Wesley,2004年。
翻译自: https://medium.com/@ronlauhk01/exception-safety-in-js-world-7f1e980c409b
js 捕获异常获取异常信息