For AI agents: the complete documentation index is available at /tc39-atlas/en/llms.txt, the full documentation bundle is available at /tc39-atlas/en/llms-full.txt, and this page is available as Markdown at /tc39-atlas/en/proposals/stage/2/proposal-throw-expressions.md.
  • English
  • throw expressions S2

    Proposal details
    Proposal overview

    This proposal introduces a throw expression to allow throwing exceptions in expression contexts like parameter initializers, arrow functions, and conditionals. It differs from the throw statement in precedence, hence forbidding certain binary operators to its right unless parenthesized.

    Note

    The README below comes from the upstream repository and may contain outdated stage or status metadata. Use the proposal details above as the current source of truth.

    ECMAScript throw expressions

    This proposal defines new syntax to throw exceptions from within an expression context.

    Status

    Stage: 2
    Champion: Ron Buckton (@rbuckton)

    For more information see the TC39 proposal process.

    Authors

    • Ron Buckton (@rbuckton)

    Proposal

    A throw expression allows you to throw exceptions in expression contexts. For example:

    • Parameter initializers
      function save(filename = throw new TypeError("Argument required")) {
      }
    • Arrow function bodies
      lint(ast, { 
        with: () => throw new Error("avoid using 'with' statements.")
      });
    • Conditional expressions
      function getEncoder(encoding) {
        const encoder = encoding === "utf8" ? new UTF8Encoder() 
                      : encoding === "utf16le" ? new UTF16Encoder(false) 
                      : encoding === "utf16be" ? new UTF16Encoder(true) 
                      : throw new Error("Unsupported encoding");
      }
    • Logical operations
      class Product {
        get id() { return this._id; }
        set id(value) { this._id = value || throw new Error("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:

    throw a ? b : c; // evaluates 'a' and throws either 'b' or 'c'
    (throw a ? b : c); // without restriction would throw 'a', so `?` is forbidden
    
    throw a, b; // evaluates 'a', throws 'b'
    (throw a, b); // would throw 'a', not 'b', so `,` is forbidden
    
    throw a && b; // throws 'a' if 'a' is falsy, otherwise throws 'b'
    (throw a && b); // would always throw 'a', so `&&` is forbidden
    
    throw a || b; // throws 'a' if 'a' is truthy, otherwise throws 'b'
    (throw a || 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:

    const x = a ? throw b : c; // if 'a' then throw 'b', else evaluate 'c'

    Grammar

    ++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] `;`

    Other Notes

    A throw expression can be approximated in ECMAScript using something like the following definition:

    const __throw = err => { throw err; };
    
    // via helper...
    function getEncoder1(encoding) {
      const encoder = encoding === "utf8" ? new UTF8Encoder() 
                    : encoding === "utf16le" ? new UTF16Encoder(false) 
                    : encoding === "utf16be" ? new UTF16Encoder(true) 
                    : __throw(new Error("Unsupported encoding"));
    }
    
    // via arrow...
    function getEncoder2(encoding) {
      const encoder = encoding === "utf8" ? new UTF8Encoder() 
                    : encoding === "utf16le" ? new UTF16Encoder(false) 
                    : encoding === "utf16be" ? new UTF16Encoder(true) 
                    : (() => { throw new Error("Unsupported encoding"); })();
    }

    However, this has several downsides compared to a native implementation:

    • The __throw helper will appear in err.stack in a host environment.
      • This can be mitigated in some hosts that have Error.captureStackTrace
    • Hosts require more information for optimization/deoptimization decisions as the throw is 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).

    Resources

    TODO

    The following is a high-level list of tasks to progress through each stage of the TC39 proposal process:

    Stage 1 Entrance Criteria

    • 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).

    Stage 2 Entrance Criteria

    Stage 3 Entrance Criteria

    Stage 4 Entrance Criteria

    • 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.