Why doesn't logical OR work with error throwing in JavaScript?

Javascript

Javascript Problem Overview


This is a pretty common and useful practice:

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

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

But it doesn't work (SyntaxError) when throwing an error:

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

Is there a way how to achieve the same effect in a similarly elegant way? This is IMHO a lot of boilerplate code:

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

Or is there any theoretical obstruction, why this is not, and never will be, possible?

Javascript Solutions


Solution 1 - Javascript

throw is a statement only; it may not exist in a position where an expression is required. For similar reasons, you can't put an if statement there, for example

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

is invalid syntax as well.

Only expressions (things that evaluate to a value) are permitted to be assigned to variables. If you want to throw, you have to throw as a statement, which means you can't put it on the right-hand side of an assignment.

I suppose one way would be to use an IIFE on the right-hand side of the ||, allowing you to use a statement on the first line of that function:

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

But that's pretty weird. I'd prefer the explicit if - throw.

Solution 2 - Javascript

Your problem is that an assignment expects an expression but you give it a statement

The Syntax for initializing/assigning a variable is:

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

but you use

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

which is invalid Syntax.

Expressions

An expression is something that produces a value.

What is a "value"?

A value is anything that is a type in Javascript

  • Numbers
  • Strings
  • Booleans
  • Objects
  • Arrays
  • Symbols

Examples for Expressions:

Literals
var x = 5;

x is assigned the value "5"

A function call
var x = myFunc();

myFunc() produces a value that is assigned to x

The produced value of a function is its return value - A function always returns, and if it doesn't explicitly, it returns undefined.

Functions have the added benefit of being able to contain statements in their body - Which will be the solution to your question - But more on that later.

Statements

A statement is something that performs an action. For Example:

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

This loop performs the action of executing the loop body 10 times

Throwing an error
throw new Error()

Unwinds the stack and stops the execution of the current frame

So why can't we mix both?

When you want to assign to a variable, you want an expression because you want the variable to have a value.

If you think about it, it should be clear that it will never work with a statement. Giving a variable an "action" is nonsense. What is that even supposed to mean?

Therefore you cannot use the throw statement since it does not produce a value.

You can only have one or the other. Either you are (expression) something or you do (statement) something.

A fix

You can convert any statement into an expression by wrapping it in a function, I suggest using an IIFE (Immediately invoked function expression) - basically a function that invokes itself - to do just that

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

This works because the right side is now a function and a function is an expression which produces a value, The value is undefined in this case, but since we stop executing it doesnt matter anyways.

Future Possibilities

Technically there is nothing that prevents this from working.

Many languages (c++, ...) actually already treat throw as an expression. Some (kotlin, ...) even leave out statements completely and treat everything as an expression.

Others (c#, php, ...) provide workarounds like the ?? null-concealing or ?. elvis operator to solve this very use case.

Maybe in the future we get one of those features into the ecmascript standard (there is even an open proposal to include this) until then your best bet is to use a function like:

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

Solution 3 - Javascript

You could move the throwing of the exception into a function, because throw is a statement of control flow, and not an expression:

> An expression is any valid unit of code that resolves to a value.

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

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

Solution 4 - Javascript

As other answers have stated, it is because throw is a statement, which can't be used in contexts which expect expressions, such as on the right side of a ||. As stated by others, you can get around that by wrapping the exception in a function and immediately calling it, but I'm going to make the case that doing so is a bad idea because it makes your intent less clear. Three extra lines of code is not a big deal for making the intent of your code very clear and explicit. I personally think that throw being statement-only is a good thing because it encourages writing more straightforward code that is less likely to cause other developers to scratch their heads when encountering your code.

The || defaulting idiom is useful when you want to provide default or alternative values for undefined, null, and other falsy values, but I think it loses a lot of its clarity when used in a branching sense. By "branching sense", I mean that if your intent is to do something if a condition holds (the doing something in this case being throwing an exception), then condition || do_something() is really not a clear way to express that intent even though it is functionally identical to if (!condition) {do_something()}. Short-circuit evaluation isn't immediately obvious to every developer and || defaulting is only understood because it's a commonly-used idiom in Javascript.

My general rule of thumb is that if a function has side effects (and yes, exceptions count as side effects, especially since they're basically non-local goto statements), you should use an if statement for its condition rather than || or &&. You're not golfing.

Bottom line: which is going to cause less confusion?

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

or

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

It's usually worth it to sacrifice terseness for clarity.

Solution 5 - Javascript

Like others have said the problem is that throw is a statement and not an expression.

There is however really no need for this dichotomy. There are languages where everything is an expression (no statements) and they're not "inferior" because of this; it simplifies both syntax and semantic (e.g. you don't need separate if statements and the ternary operator ?:).

Actually this is just one of the many reasons for which Javascript (the language) kind of sucks, despite Javascript (the runtime environment) being amazing.

A simple work-around (that can be used also in other languages with a similar limitation like Python) is:

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

then you can simply write

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

There is some complexity in having throw as an expression for statically typed languages: what should be the static type of x() ? y() : throw(z)?; for example C++ has a very special rule for handling a throw expression in the ternary operator (the type is taken from the other branch, even if formally throw x is considered an expression of type void).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionttulkaView Question on Stackoverflow
Solution 1 - JavascriptCertainPerformanceView Answer on Stackoverflow
Solution 2 - JavascriptPatrick HollweckView Answer on Stackoverflow
Solution 3 - JavascriptNina ScholzView Answer on Stackoverflow
Solution 4 - JavascriptBeefsterView Answer on Stackoverflow
Solution 5 - Javascript6502View Answer on Stackoverflow