Using throw in a Javascript expression
Asked Answered
M

9

30

Here is what I want to do:

var setting = process.env.SETTING || throw new Error("please set the SETTING environmental variable");
                                     ^^^^^

But the interpreter complains about "Syntax Error: Unexpected token throw".

Is there any way to throw an exception in the same line that we compare whether a value is falsey or not?

Montgomery answered 3/3, 2012 at 9:32 Comment(0)
F
38

You can make use of the functional nature of javascript. A function (expression) can be invoked right away using function() {}(). That is called an Immediately Invoked Function Expression (IFFE).

x || y is called short-circuit evaluation.

var setting = process.env.SETTING || function() {
  throw "please set the SETTING environmental variable"; }();

// es201x
const setting = process.env.SETTING || ( () => {
  throw `SETTING environmental variable not set`; } )();

or more generic create a function to throw errors and use that:

function throwErr(mssg){
    throw new Error(mssg);
}

var setting = process.env.SETTING || 
               throwErr("please set the SETTING environmental variable");

A snippet I use:

const throwIf = (
  assertion = false,              // default false
  message = `An error occurred`,  // default Error message string
  ErrorType = Error               // default errorType generic Error
) => {
    // throw if assertion is true, otherwise do nothing
    if (assertion) {
      throw new ErrorType(message);
    }
};
const windowSettingSet = window.SOMESETTING;
// !windowSettingSet is true, so will throw
throwIf(!windowSettingSet, `window.SOMESETTING not defined`, TypeError);
Faxon answered 3/3, 2012 at 9:38 Comment(3)
+1 for an interesting solution, even though it seems a bit overkill.Inellineloquent
IIFEs aren't too uncommon in Javascript. I think this also becomes a little nicer-looking with arrow functions: var setting = process.env.SETTING || (() => {throw "please set $SETTING";})()Claudicant
@Alec you're right, I love how it looks super clean. My only reservation to this answer in general is people reading the code will have no idea what's happening, it becomes another bit of black box magic. beginners could even come away thinking that's how throw works in js. Some day they'll try it themselves, and waste lots of time unraveling where they learned it from and why it no longer works.Lymphadenitis
I
23

throw is a statement, not an expression, so you cannot use it as part of another expression. You have to split it:

var setting = process.env.SETTING;
if (!setting) throw new Error("please set the SETTING environmental variable");
Inellineloquent answered 3/3, 2012 at 9:35 Comment(0)
S
10

There is a draft proposal to introduce throw expressions (similar to C# 7's throw expressions) to JavaScript/ECMAScript:

TC39 proposal: Throw Expressions

Surrogate answered 12/4, 2018 at 19:55 Comment(1)
GitHub link: github.com/tc39/proposal-throw-expressionsPossibly
T
4

just wrap it with braces

{throw ...}

or wrap it with a function (witch actually wraps it inside braces)

()=>{throw ..}
//or:
function(){throw..}
Tojo answered 27/11, 2018 at 10:44 Comment(1)
The first doesn't work (Uncaught SyntaxError: Unexpected identifier), and the second doesn't do what the OP's asking for unless you (1) wrap it in braces so as not to confuse the parser and (2) add more braces to actually call the function: setting = … || (() => {throw …})()Hutton
R
1

I see you using nodejs.

I think the best way to validate input parameters is assertion. NodeJS have built-in simple assertion module: http://nodejs.org/api/assert.html

And use it, for example, like this:

var assert = require('assert');

// ...

function myFunction (param) {
    assert(param, 'please pass param');
    // ...
}

For test environment you can do it like these:

require('assert')(process.env.setting, 'please set the SETTING environmental variable');

Or:

;(function (a) {
    a(process.env.setting, 'please set the SETTING environmental variable');
    a(process.env.port, 'please set the PORT environmental variable');
}(require('assert')));
Raffin answered 3/3, 2012 at 9:32 Comment(0)
R
1

throw does not have a return value. So you can not use it like that. However you can wrap the throw in a function. It'll work then. But itll be tougher to find the source of exception.

Rosner answered 3/3, 2012 at 9:48 Comment(3)
Chrome's debugger supports a 'breakpoint on exception' behaviour.Enucleate
-1 This is false and does not answer the question. Everything has a return value in JavaScript, no? And if you don't return anything you just get undefined. Even delete, try var foo = ""; alert(delete foo);. It's just that throw breaks the flow, so you can't really get and use its return type.Insoluble
@Insoluble you are incorrect. Nothing in js 'returns a value' except a function call. Expressions evaluate to a value, but don't return a value. What's the difference? 'return' is a specific keyword that refers only to a function call result. Statements, like 'throw' or 'var', definitely don't return a value. Saying 'throw' returns a value is like saying 'var' returns a value. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Lymphadenitis
S
1
const getenv = require('getenv');
const setting = getenv('SETTING');

If SETTING does not exist you’ll get:

… getenv.js:14
throw new Error('GetEnv.Nonexistent: ' + varName + ' does not exist ' +
^ Error: GetEnv.Nonexistent: SETTING does not exist and no fallback value provided.

BTW my experience tells me that default values for environment variables are evil.

Steak answered 11/2, 2017 at 14:26 Comment(1)
My experience also tells me that assigning default environment variables is evil.Interlock
M
1

Not a direct answer to the question, but if you're using TypeScript I've found runtypes to be a great alternative to manually validating and throwing. While the approach in the OP works when the input type is output | null | undefined or something similar, runtypes also works when the input type is any, such as when defensively decoding a JSON with a known structure.

You would use it like so:

import { String } from 'runtypes';
const setting = String.check(process.env.SETTING)

It'll throw if process.env.SETTING is undefined. Add .withConstraint() to add other conditions. The library is actually quite powerful and can validate not only simple types but records (interfaces), unions, etc. in a type-safe way.

One drawback is that you don't get to customize the error message ("Failed constraint check"), which is OK in a lot of fast-and-dirty cases because the line the error occurred will tell you what the problem was.

io-ts is another similar library, though it seems to have a more complicated approach and is in flux at the moment.

Montgomery answered 25/5, 2020 at 4:33 Comment(0)
M
0

While it's not possible to use throw directly as an expression, it can be used in an immediately-called anonymous closure as Koooilnc suggests, or separated into its own statement as casablanca suggests, or replaced with Node's assert as Alex Yarochevich suggests.

However, probably the most common and idiomatic way of doing this today is by using tiny-invariant or a similar function.

invariant(process.env.SETTING, "SETTING environment variable is required");
const setting = process.env.SETTING;

or

const setting = process.env.SETTING;
invariant(setting, "SETTING environment variable is required");

It's simpler than an immediately-called anonymous closure, more clear and shorter than a separate if-throw statement (because the condition is not inverted), and appropriately narrows types in TypeScript unlike Node's assert and can be used in the browser.

It's also much more light-weight than my previous suggestion of using a runtime types library.

Montgomery answered 15/1, 2023 at 19:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.