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/4/proposal-private-fields-in-in.md.
  • English
  • Ergonomic brand checks for Private Fields S4

    Proposal details
    Proposal overview

    This proposal addresses the problem of checking whether an object has a private field without throwing exceptions, which currently requires awkward try/catch patterns. The main solution introduces the in operator to test the presence of private fields, e.g., #brand in obj.

    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.

    Ergonomic brand checks for Private Fields

    EcmaScript Proposal, specs, and reference implementation to provide brand checks without exceptions.

    Spec drafted by @ljharb.

    Built on top of https://github.com/tc39/ecma262/pull/1668/ pending Class Fields being stage 4 and merged into the larger spec.

    This proposal is currently at stage 4 of the process.

    Rationale

    Private fields have a built-in ”brand check”, in that if you try to access a private field on an object that does not have it installed, it throws an exception.

    This is great! However, in order to keep the existence of a private field actually private, I often want to check if an object has a private field, and if not, have some fallback behavior (which might even be throwing a custom exception, with a message that does not reveal that I’m using a private field as my mechanism).

    Using try/catch for this does work, but is quite awkward:

    class C {
      #brand;
    
      static isC(obj) {
        try {
          obj.#brand;
          return true;
        } catch {
          return false;
        }
      }
    }

    This is also an issue for getters (that might throw):

    class C {
      #data = null; // populated later
    
      get #getter() {
        if (!this.#data) {
          throw new Error('no data yet!');
        }
        return this.#data;
      }
    
      static isC(obj) {
        try {
          obj.#getter;
          return true;
        } catch {
          return false; // oops! might have gotten here because `#getter` threw :-(
        }
      }
    }

    What is desired is a simple solution to produce a boolean indicating the presence of a private field, that does not require a try/catch or exceptions.

    Solution:

    in

    The most obvious solution is using the in keyword, like so:

    class C {
      #brand;
    
      #method() {}
    
      get #getter() {}
    
      static isC(obj) {
        return #brand in obj && #method in obj && #getter in obj;
      }
    }

    This has no ASI hazards I am aware of; however, it does have one important tradeoff: it permanently kills any possibility for private field shorthand. Specifically, this is because if #brand (in the previous example) is a shorthand for this.#brand, then (#brand) in obj would have to mean the same as this.#brand in obj, which means "is the value of this.#brand in obj?", not "does obj have the private field #brand?".

    Rejected solutions:

    try statement

    Another alternative is:

    class C {
      #brand;
    
      static isC(obj) {
        return try obj.#brand;
      }
    }

    However, this strongly suggests that try <expression> would be a generic syntax for "if it throws an exception, produce false, otherwise produce true". That would be a much larger proposal, and would perhaps be a bit overkill to solve the specific problem around private fields.

    This would require a lookahead restriction for a curly brace (which would mean that the try expression couldn't be an object literal, unless it was wrapped in parens).

    Spec

    You can view the spec for the in solution rendered as HTML.

    Implementations