【问题标题】:Why doesn't logical OR work with error throwing in JavaScript?为什么逻辑或不适用于 JavaScript 中的错误抛出?
【发布时间】:2019-09-04 18:02:48
【问题描述】:

这是一种非常常见且有用的做法:

// default via value
var un = undefined
var v1 = un || 1

// default via a function call
var myval = () => 1
var v2 = un || myval()

但是在抛出错误时不起作用(SyntaxError):

var v3 = un || throw new Error('un is not set!')

有没有办法以同样优雅的方式实现相同的效果? 恕我直言,这是很多样板代码:

if (!un) {
    throw new Error('un is not set!')
}
var v3 = un

或者是否存在任何理论上的障碍,为什么这不可能,也永远不可能?

【问题讨论】:

  • 这看起来很像 PHP(或 Ruby?),您通常会在其中执行类似 $dbHandle = connectToDB() OR die("couldn't connect") 的操作。不过,我不记得 PHP 是否允许 throw 语句作为该构造的一部分。
  • @VLAZ die() 看起来像一个函数。函数也是 JavaScript 中的表达式。
  • 当然,但我说的是“一行错误”的一般模式。它让我想起了很多你在 PHP 中是如何做到这一点的——do() OR die() 很常见,至少在我编写 PHP 时是这样。试图在与另一个表达式相同的行中抛出一个异常让我印象深刻。
  • 除了答案:还有a proposal 让这成为可能,所以也许很快我们就会有throw 表达式。
  • @vsemozhetbyt 感谢您的评论,这是我问题的实际答案!

标签: javascript


【解决方案1】:

throw 只是一个声明;它可能不存在于需要 表达式 的位置。出于类似的原因,您不能在此处放置 if 语句,例如

var something = false || if (cond) { /* something */ }

也是无效的语法。

只有表达式(评估为值的东西)被允许分配给变量。如果你想throw,你必须throw作为一个语句,这意味着你不能把它放在赋值的右边。

我想一种方法是在|| 的右侧使用 IIFE,允许您在该函数的第一行使用语句:

var un = undefined
var v2 = un || (() => { throw new Error('nope') })();

但这很奇怪。我更喜欢明确的if - throw

【讨论】:

  • 值得指出的是,throw可以被做成一个表达式。也许未来的语言版本会启用这种模式。
  • 其实已经有一个stage 2 proposal可以在语言中添加throw表达式。唉,它还需要一段时间才能使用。
  • 我们的项目——在 Typescript 中,所以模式可能更透明——我们有一个核心实用模块,其中包括 export function crash(message: string): never { throw new Error(message); },所有的抛出都是通过这个函数完成的。有用,因为现在它是一个表达式(never return 注释表明它不会返回,因为它抛出),并且因为我们可以在其中放置一个断点(我们有一个高级 catch 块来生成自定义错误消息而不是而不是仅仅发送到控制台,但这可以防止调试器在抛出时中断)。
  • throw 表达式的值是多少?
  • @amn 如果您询问提案,解释器将永远无法到达那个点 - 在评估之前 while 正在评估表达式时抛出错误已完成,因此表达式评估永远不会完成 - 因此,说它评估为任何东西都是毫无意义的。
【解决方案2】:

你的问题是赋值需要一个表达式,但你给它一个语句

初始化/分配变量的语法是:

var|let|const <variableName> = <expression>

但你使用

var|let|const <variableName> = <statement>

这是无效的语法。

表达式

表达式是产生值的东西。

什么是“价值”?

值是 Javascript 中的任何类型

  • 数字
  • 字符串
  • 布尔值
  • 对象
  • 数组
  • 符号

表达式示例:

文字

var x = 5;

x 被赋值为“5”

函数调用

var x = myFunc();

myFunc() 产生一个赋值给 x 的值

一个函数的产生值就是它的返回值——一个函数总是返回,如果它没有显式地返回,它返回undefined

函数的额外好处是能够在其主体中包含语句 - 这将是您问题的解决方案 - 但稍后会详细介绍。

声明

语句是执行动作的东西。例如:

一个循环

for (var i = 0; i < 10; i++) { /* loop body */ }

这个循环执行循环体执行10次的动作

抛出错误

throw new Error()

展开堆栈并停止当前帧的执行

那么为什么我们不能同时使用两者呢?

当你想给一个变量赋值时,你需要一个表达式,因为你希望变量有一个值。

如果您考虑一下,应该清楚它永远不会与语句一起使用。给变量一个“动作”是无稽之谈。这到底是什么意思?

因此您不能使用throw 语句,因为它不会产生值。

你只能有一个或另一个。 要么你 are (expression) 什么,要么你 do (statement) 什么。

修复

您可以通过将任何语句包装在函数中来将其转换为表达式,我建议使用IIFE (Immediately invoked function expression)(基本上是一个调用自身的函数)来做到这一点

var x = 5 || (() => throw new Error())()

这是可行的,因为右侧现在是一个函数,而一个函数是一个产生值的表达式,在这种情况下,该值是 undefined,但由于我们停止执行,所以无论如何都没关系。

未来的可能性

从技术上讲,没有什么可以阻止它的工作。

许多语言(c++,...)实际上已经将throw 视为表达式。有些 (kotlin, ...) 甚至完全省略了语句,并将所有内容都视为表达式。

其他(c#、php、...)提供解决方法,例如 ?? null-concealing 或 ?. elvis 运算符来解决这个用例。

也许将来我们会将这些功能之一纳入 ecmascript 标准 (there is even an open proposal to include this) 直到那时您最好的选择是使用如下功能:

function assertPresent(value, message)
{
  if(!value) {
    throw new Error(message);
  } else {
    return value;
  }
}

【讨论】:

  • 顺便说一句,C# 6 允许“throw”成为一个表达式,以实现这样的场景——表达式的返回类型是从上下文中推断出来的。我想像这样的东西在概念上更容易添加到 JavaScript,因为它不会在编译时检查返回类型。
  • 吹毛求疵,这是 || 运算符,这里需要两个表达式,而不是赋值。
  • 这也是一个表达式(void 类型)in C++
【解决方案3】:

您可以将异常的抛出移到一个函数中,因为throw 是控制流的statement,而不是expression

表达式是解析为值的任何有效代码单元。

const throwError = function (e) { throw new Error(e); };

var un = undefined,
    v3 = un || throwError('un is not set!');

【讨论】:

  • 为什么不直接拥有const throwf = function(err) { throw err },然后它可以在任何地方使用。
  • 注意:throw expressions 处于添加到语言的过程的第 2 阶段。 :-) 直到/除非发生这种情况,像上面的(更新的)这样的函数包装器是简单的解决方法。
  • 抛出现在发生在其他地方,throwError 出现在调用堆栈中。
【解决方案4】:

正如其他答案所述,这是因为throw 是一个语句,不能在需要表达式的上下文中使用,例如|| 的右侧。正如其他人所说,您可以通过将异常包装在一个函数中并立即调用它来解决这个问题,但我要说明这样做是一个坏主意,因为它会使您的意图不太清楚。三行额外的代码对于使代码的意图非常清晰和明确来说并不是什么大问题。我个人认为 throw 是仅语句是一件好事,因为它鼓励编写更简单的代码,这样就不太可能导致其他开发人员在遇到您的代码时摸不着头脑。

|| 默认习语在您想为undefinednull 和其他虚假值提供默认值或替代值时很有用,但我认为在分支意义上使用它会失去很多清晰度.通过“分支意义”,我的意思是如果您的意图是在条件成立的情况下做某事(在这种情况下做某事是抛出异常),那么condition || do_something() 真的不是一个明确的方法表达这个意图,即使它在功能上与if (!condition) {do_something()} 相同。短路评估对每个开发人员来说并不是立即显而易见的,|| 默认之所以被理解,是因为它是 Javascript 中的一个常用习语。

我的一般经验法则是,如果一个函数有副作用(是的,异常算作副作用,特别是因为它们基本上是非本地 goto 语句),你应该使用 if 语句来表示它的条件,而不是||&amp;&amp;。你不是在打高尔夫球。

底线:哪个会减少混乱?

return value || (() => {throw new Error('an error occurred')})()

if (!value) {
    throw new Error('an error occurred')
}
return value

为了清晰而牺牲简洁性通常是值得的。

【讨论】:

  • 异常确实是一种副作用,但是 - 如果不用于流量控制 - 它只是一个异常(可能导致系统崩溃,这很好),而不是流量控制。使用if 语句会吸引读者的眼球,并在心理上将异常变成流控制,这是错误的,因为不应以这种方式使用异常。按照我的建议写的,对断言做了一个例外,这样更好(读者很容易忽略)。但也许最好的办法是根本不使用此类检查,让运行时本身处理错误的参数......
  • @ttulka 如果你的意图是有一个断言,你最好在某个地方定义一个 assert 函数来包装这个意图。像function assert(value, message) { if (value) {return value} else {throw new Error(message || 'assertion failed')}} 这样的东西就可以了。或者您可以使用断言库。您甚至可以使用 assert = () =&gt; {} 删除发布版本的断言
  • 即使异常不用于流控制,它仍然有效地充当goto somewhere,而catch 语句实际上是comefroms。现在这在很多情况下都是合理的,尤其是在 JS 中它们更像panics 并在某种调度程序的顶部被捕获时,但是如果没有在 API 边界处捕获异常,通常会导致意外。
  • 我觉得不一样。如果我不进行显式检查,无论如何都会抛出异常,我只想“自定义”它。 assert 的想法是可以的,但它与其他答案中的提议没有什么不同。我的动机是直接使用语言结构而不需要任何样板代码。
  • if 语句不是样板文件。如果您所做的只是抛出异常,您甚至不必包含大括号。然后你可以保持在同一行。
【解决方案5】:

就像其他人所说的那样,问题在于throw 是一个语句而不是一个表达式。

然而,这种二分法确实没有必要。在有些语言中,一切都是表达式(没有语句),因此它们并不“劣质”;它简化了语法和语义(例如,您不需要单独的 if 语句和三元运算符 ?:)。

实际上,这只是 Javascript(语言)糟糕的众多原因之一,尽管 Javascript(运行时环境)非常棒。

一个简单的解决方法(也可以在具有类似限制的其他语言中使用,例如 Python)是:

function error(x) { throw Error(x); }

那么你可以简单地写

let x = y.parent || error("No parent");

throw 作为静态类型语言的表达式存在一些复杂性:x() ? y() : throw(z) 的静态类型应该是什么?例如,C++ 有一个非常特殊的规则来处理三元运算符中的 throw 表达式(该类型取自另一个分支,即使正式地将 throw x 视为 void 类型的表达式)。

【讨论】:

猜你喜欢
  • 2019-09-04
  • 2021-04-05
  • 2013-07-08
  • 2011-05-31
  • 2021-11-29
  • 2018-09-13
  • 2013-06-30
  • 2019-05-21
  • 1970-01-01
相关资源
最近更新 更多