This proposal defines new syntax to throw exceptions from within an expression context.
Stage: 2
Champion: Ron Buckton (@rbuckton)
For more information see the TC39 proposal process.
- Ron Buckton (@rbuckton)
A throw expression allows you to throw exceptions in expression contexts. For example:
- Parameter initializers
functionsave(filename=thrownewTypeError("Argument required")){}
- Arrow function bodies
lint(ast,{with: ()=>thrownewError("avoid using 'with' statements.")});
- Conditional expressions
functiongetEncoder(encoding){constencoder=encoding==="utf8" ? newUTF8Encoder() : encoding==="utf16le" ? newUTF16Encoder(false) : encoding==="utf16be" ? newUTF16Encoder(true) : thrownewError("Unsupported encoding");}
- Logical operations
classProduct{getid(){returnthis._id;}setid(value){this._id=value||thrownewError("Invalid value");}}
A throw expression does not replace a throw statement due to the difference in the precedence of their values. To maintain the precedence of the throw statement, we must add a lookahead restriction to ExpressionStatement to avoid ambiguity.
Due to the difference in precedence between a throw expression and a ThrowStatement, certain operators to the right of the expression would parse differently between the two which could cause ambiguity and confusion:
throwa ? b : c;// evaluates 'a' and throws either 'b' or 'c'(throwa ? b : c);// without restriction would throw 'a', so `?` is forbiddenthrowa,b;// evaluates 'a', throws 'b'(throwa,b);// would throw 'a', not 'b', so `,` is forbiddenthrowa&&b;// throws 'a' if 'a' is falsy, otherwise throws 'b'(throwa&&b);// would always throw 'a', so `&&` is forbiddenthrowa||b;// throws 'a' if 'a' is truthy, otherwise throws 'b'(throwa||b);// would always throw 'a', so `||` is forbidden// ... etc.As a result, all binary operators and the ? operator are forbidden to the right of a throw expression. To use these operators inside of a throw expression, the expression must be surrounded with parentheses:
(throw(a,b));// evaluates 'a', throws 'b'(throw(a ? b : c));// evaluates 'a' and throws either 'b' or 'c'However, we do not forbid : so that a throw expression can still be easily used in a ternary:
constx=a ? throwb : c;// if 'a' then throw 'b', else evaluate 'c'++ThrowExpressionInvalidPunctuator : one of `,` `<` `>` `<=` `>=` `==` `!=` `===` `!==` `+` `-` `*` `/` `%` `**` `<<` `>>` `>>>` `&` `|` `^` `&&` `||` `??` `=` `+=` `-=` `*=` `%=` `**=` `<<=` `>>=` `>>>=` `&=` `|=` `^=` `&&=` `||=` `??=` `?` UnaryExpression[Yield, Await] : ++ `throw` UnaryExpression[?Yield, ?Await] [lookahead ∉ ThrowExpressionInvalidPunctuator] ExpressionStatement[Yield, Await] : -- [lookahead ∉{`{`, `function`, `async` [no |LineTerminator| here] `function`, `class`, `let [`}] Expression[+In, ?Yield, ?Await] `;`++ [lookahead ∉{`{`, `function`, `async` [no |LineTerminator| here] `function`, `class`, `let [`, `throw`}] Expression[+In, ?Yield, ?Await] `;`A throw expression can be approximated in ECMAScript using something like the following definition:
const__throw=err=>{throwerr;};// via helper...functiongetEncoder1(encoding){constencoder=encoding==="utf8" ? newUTF8Encoder() : encoding==="utf16le" ? newUTF16Encoder(false) : encoding==="utf16be" ? newUTF16Encoder(true) : __throw(newError("Unsupported encoding"));}// via arrow...functiongetEncoder2(encoding){constencoder=encoding==="utf8" ? newUTF8Encoder() : encoding==="utf16le" ? newUTF16Encoder(false) : encoding==="utf16be" ? newUTF16Encoder(true) : (()=>{thrownewError("Unsupported encoding");})();}However, this has several downsides compared to a native implementation:
- The
__throwhelper will appear inerr.stackin a host environment.- This can be mitigated in some hosts that have
Error.captureStackTrace
- This can be mitigated in some hosts that have
- Hosts require more information for optimization/deoptimization decisions as the
throwis not local to the function. - Not ergonomic for debugging as the frame where the exception is raised is inside of the helper.
- Inline invoked arrow not ergonomic (at least 10 more symbols compared to native).
The following is a high-level list of tasks to progress through each stage of the TC39 proposal process:
- Identified a "champion" who will advance the addition.
- Prose outlining the problem or need and the general shape of a solution.
- Illustrative examples of usage.
High-level API(proposal does not introduce an API).
- Initial specification text.
- Optional. Transpiler support.
- Complete specification text.
- Designated reviewers have signed off on the current spec text.
- The ECMAScript editor has signed off on the current spec text.
- Test262 acceptance tests have been written for mainline usage scenarios and merged.
- Two compatible implementations which pass the acceptance tests: [1], [2].
- A pull request has been sent to tc39/ecma262 with the integrated spec text.
- The ECMAScript editor has signed off on the pull request.