为什么逻辑或不能与JavaScript中的错误抛出一起使用?

时间:2019-02-11 07:52:57

标签: javascript

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

// 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

还是有任何理论上的障碍,为什么这不可能,并且永远不可能?

5 个答案:

答案 0 :(得分:80)

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

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

也是无效的语法。

仅将表达式(评估为值的内容)分配给变量。如果您想throw,则必须throw作为语句,这意味着您不能将其放在作业的右侧。

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

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

但是那很奇怪。我希望使用显式if-throw

答案 1 :(得分:32)

您的问题是分配需要一个表达式,但是您给它一个语句

用于初始化/分配变量的语法为:

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

但您使用

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

这是无效的语法。

表达式

表达式是产生值的东西。

什么是“值”?

值是Java语言中的类型

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

表达式示例:

文学

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())()

之所以可行,是因为右侧现在是一个函数,而函数是一个产生值的表达式。

未来的可能性

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

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

其他人(c#,php等)提供了??空值隐藏或?.猫王运算符之类的解决方法来解决这种用例。

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

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

答案 2 :(得分:19)

您可以将引发异常的功能转移到函数中,因为throwstatement的控制流,而不是expression

  

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

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

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

答案 3 :(得分:3)

正如其他答案所指出的,这是因为throw是一条语句,不能在需要表达式的上下文中使用,例如||的右侧。如其他人所述,您可以通过将异常包装在函数中并立即调用它来解决此问题,但是我将证明这样做不是一个好主意,因为它会使您的意图不那么清楚。要使代码的意图非常清晰明了,多余的三行代码并不重要。我个人认为throw仅用作语句是一件好事,因为它鼓励编写更直接的代码,而在遇到您的代码时不太可能引起其他开发人员的注意。

当您想为||undefined和其他虚假值提供默认值或替代值时,null默认习惯用法很有用,但我认为它失去了很多清晰度当以分支方式使用时。所谓“分支意识”,是指如果您的意图是在条件成立的情况下做某事(在这种情况下,做某事会抛出异常),那么condition || do_something()确实不是即使在功能上与if (!condition) {do_something()}相同的表达意图的清晰方法。短路评估并不是对每个开发人员都立即显而易见的,并且||默认是唯一可以理解的,因为它是Java语言中常用的习惯用法。

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

底线:哪个会减少混乱?

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

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

为了清楚起见,牺牲简洁通常是值得的。

答案 4 :(得分:1)

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

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

实际上,尽管Javascript(运行时环境)令人赞叹,但这只是Java语言(一种语言)令人反感的众多原因之一。

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

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

然后您只需编写

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

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