这是一种非常常见且有用的做法:
// 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
还是有任何理论上的障碍,为什么这不可能,并且永远不可能?
答案 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)
您可以将引发异常的功能转移到函数中,因为throw
是statement的控制流,而不是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
的表达式,该类型也是从另一个分支获取的)。>