Skip to content

[Suggestion] Multiple catch-blocksΒ #53965

@jeengbe

Description

@jeengbe

Suggestion

πŸ” Search Terms

Multiple catch, catch instanceof, typed catch

βœ… Viability Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

πŸ“ƒ Motivating Example

try {
  JSON.parse(fs.readFileSync('save.json'));
} catch SyntaxError (err) {
  // Corrupt save file; do nothing
}
// all other (fs) errors are nothing we want to deal here with and just pass right though

Previously, this would have required a much more nested and verbose body, such as:

try {
  JSON.parse(fs.readFileSync('save.json'));
} catch (err) {
  if (err instanceof SyntaxError) {
    // Corrupt save file; do nothing
  } else {
    throw err;
  }
}

⭐ Suggestion

This suggestion extends the syntax of the catch statement by allowing one or more identifiers to restrict the type of errors that would be caught:

try {
  someFunction();
} catch ErrorA (_err) {
  console.log('Got error A')
} catch ErrorB | ErrorC (error) {
  console.log('Got error B or C', error.message)
} catch (_err) {
  console.log('Not sure what happened')
}

This would roughly translate to:

try {
  someFunction();
} catch (_e1) {
  //     ^ free emit helper variable
  if (_e1 instanceof ErrorA) {
    let _err = _e1;
    console.log('Got error A')
  } else if (_e1 instanceof ErrorB || _e1 instanceof ErrorC) {
    let error = _e1;
    console.log('Got error B or C', error.message)
  } else {
    console.log('Not sure what happened')
  }
}

To allow for multi-catch blocks, identifiers grouped with a | b | c translate to ||'ed instanceof expressions.

If no catch block without annotation is provided, the error is re-thrown:

try {
  JSON.parse(fs.readFileSync('save.json'));
} catch SyntaxError (err) {
  // Corrupt save file; do nothing
}

turns into

try {
  JSON.parse(fs.readFileSync('save.json'));
} catch (_e1) {
  if(_e1 instanceof SyntaxError) {
    // Corrupt save file; do nothing
  } else {
    throw _e1;
  }
}

No catch blocks may come after the "untyped" catch block, or else an unrecoverable syntax error is raised.


The types of the identifiers provided must satisfy the conditions for being used on the right-hand side of an instanceof expression and not be any or unknown.

If one of the provided annotations is of type any or unknown, a semantic error is raised.


For annotated catch blocks:

If not provided, the type of the variable of the catch statement is inferred to the type of the identifier used to restrict the clause.
If provided, it must be a supertype of that type or a semantic error is raised.

Type inference of the unannotated catch block (i.e. catch (err: unknown)) is not altered, hereby making the proposal fully backwards-compatible.

πŸ’» Use Cases

The goal of this proposal aligns with other languages' goals that also support multiple catch blocks:

Better code readability:

  • Different errors are "separated and decoupled more"
  • Familiar structure (other languages support multiple catch blocks)
  • One less level of indentation
  • Less if-instanceof-else boilerplate code

I would be willing to work on a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Out of ScopeThis idea sits outside of the TypeScript language design constraintsSuggestionAn idea for TypeScript

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions