Skip to content

Experimental support of Optional Chaining (at least static property access) via experimentalOptionalChaining #30167

Closed

Description

@nodkz

Suggestion

Add experimentalOptionalChaining property to compilerOptions which will support at least static property access – the most required and expected feature.

Use Cases

For me, in 2019 the most important part of Optional Chaining is static property access. It will greatly improve DX for GraphQL client apps and make client code more robust and clean. 🚀

Current shortcomings:

  • if-check-hell 🤢
  • unchecked dottedPaths as string 💩
    • _.get(obj, ['some.nested.field']) with lodash.get
    • objectPath.get(obj, 'dottedPath') with object-path (900k weekly downloads 😱)

Examples

Assume we have following GraphQL response from the server on the client side:

interface IResponse {
  viewer?: {
    article?: {
      title?: string
    }
  }
}

const response: IResponse = await apolloClient.query(`query {
    viewer {
      article(id: 15) {
        title
      }
    }
}`);

On any level, graphql may return null, so before displaying title we must check viewer and article existence.

... with if-check-hell 🤢

if (response && response.viewer && response.viewer.article && response.viewer.article.title) {
  // ... display title
}

Pros: statically checked (if query changed, then we will be warned about un-existed props)
Cons: to much code

... with unchecked dottedPaths as string 💩

if (_.get(response, ['viewer.article.title'])) {
  // ... display title
}

Pros: quite clean code
Cons: statically UNchecked (if query changed, then we will NOT be warned about un-existed props checks)

... with Optional Chaining ❤️

if (response?.viewer?.article?.title) {
  // ... display title
}

Pros: very clean code & static checks
Cons: not implemented in TS, but already implemented about a year ago in Flow 0.74, Eslint, Babel.

Recap

We just have 3 levels deep query, but in most cases it's deeper.

if (response && response.viewer && response.viewer.article && response.viewer.article.title) {
if (_.get(response, ['viewer.article.title'])) {
if (response?.viewer?.article?.title) {

Definitely, OptionalChaining is the winner 🥇

Suggestion: repeat once more

Just add static property access from optional chaining proposal under experimentalOptionalChaining flag. This part is stable in the proposal and it is the most required feature in 2019.

Let's keep problematic optional chaining for function calling, short-circuiting, grouping, deletion, template literals – out of the scope of this issue.

Please do not reference this issue as a duplicate for issue #16 from the 2014 year, here are 2019 and requirements changed from that time.

Search Terms

Optional Chaining, null propagation.

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, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

If a miracle will happen 😂

If you like the fact that optional chaining was shaken off the dust from #16 and may even be implemented in the near future – send a SWAG from your company to the following address

050010, Kazakhstan, Almaty,
Kabanbay Batyra 21 apt 3,
Mobile +7707234004
Pavel Chertorogov

or just give a 👍

Activity

  1. nodkz commented on Mar 1, 2019

    @nodkz
    Author

    I'm prepared a simple video demo with Flow and Optional Chaining: https://youtu.be/NzlDE3cWnDc

    Again: I'm telling about optional chaining for reading properties from objects.

    PS. Let's keep problematic optional chaining for function calling, short-circuiting, grouping, deletion, template literals – out of the scope of this issue.

  2. lifeart commented on Mar 1, 2019

    @lifeart

    Why this:

    const value = this?.foo?.bar?.baz !== undefined ? this.foo.bar.baz : defaultValue;

    Better than this?

    const value = get(this, 'foo.bar.baz', defaultValue)
  3. nodkz commented on Mar 1, 2019

    @nodkz
    Author

    Alex Kanunnikov (@lifeart) please watch the video above when I breaking response interface. Such kind of things usually love to do backenders 😜

    And I want to get strongly typed code with static analysis for such kind of things, rather than old-school dot-path in a string, which not understands TypeScript.

    With optional chaining, TypeScript will help to catch such changes. 👍

  4. lifeart commented on Mar 1, 2019

    @lifeart

    Paul Damnhorn (@nodkz) this can be statically analyzed:

    const value = get(get(get(this, 'some'), 'nested'), 'key', defaultValue);
  5. nodkz commented on Mar 1, 2019

    @nodkz
    Author

    Alex Kanunnikov (@lifeart) wow! I think that it cannot be used as a good solution 😜

    In such case better to use if-check-hell 😈

  6. everdimension commented on Mar 1, 2019

    @everdimension

    I don't think that a feature like this belongs in the language.

    • Having a long chain of unknown fields should be an unlikely pattern.
    • A typed language encourages writing out typed schemas so that you don't get into situations like this.
      • A schema where each field is unknown to be present doesn't usually make much sense.
      • ...But those cases where it does make sense the problem should be explicit. But having a syntax to help you hide the unreliability of a schema will encourage creating unreliable schemas. It should be the other way around.
    • Accessing the data through long chains is likely to be a violation of the Principle of Least Knowledge
  7. Voronar commented on Mar 1, 2019

    @Voronar

    Duplicate of #16?

  8. RyanCavanaugh commented on Mar 1, 2019

    @RyanCavanaugh
    Member

    Where did this get brigaded from?

  9. john-dulaney commented on Mar 1, 2019

    @john-dulaney
  10. nodkz commented on Mar 1, 2019

    @nodkz
    Author

    Ryan Cavanaugh (@RyanCavanaugh) from twitter https://twitter.com/nodkz/status/1101432188883603456?s=19 and GraphQL community. Need to wait while US wake up for bigger feedback ;)

  11. pygy commented on Mar 1, 2019

    @pygy

    Also https://news.ycombinator.com/item?id=19283238

    FWIW, I'm against this, I'd rather have proper recursive checking for R.path(['foo', 'bar'], {foo: {bar: 5}}).

    On top of that, optional chaining is still a stage 1 proposal.

  12. dusave commented on Mar 1, 2019

    @dusave

    I will be seeking Stage 2 for Optional Chaining at the next TC-39 meetup in March which, based on the outlook coming out of our November meetup, it should reach.

  13. RyanCavanaugh commented on Mar 2, 2019

    @RyanCavanaugh
    Member

    I'm going to leave this open only because people get tend to get salty when they click on a link and see we closed something, but the definitive issue tracking this is #16 and we are (still!) committed to waiting for TC39 to decide what the semantics of this feature would be.

    After being burned multiple times by adding features to TS only to have the semantic rug pulled out from under us at the last second, there is seriously no number of upvotes that would have us adding a feature that could potentially drastically change runtime behavior at some point in the future.

    Please do not reference this issue as a duplicate for issue #16 from the 2014 year, here are 2019 and requirements changed from that time

    Nothing in that thread is not still true.

  14. tlackemann commented on Mar 2, 2019

    @tlackemann

    We have been waiting on optional chaining for years. What semantics are honestly going to change? I'm not asking in a snarky way or in an insincere way, I just honestly don't understand what could possible change from 3-years ago to now to whenever this gets implemented.

  15. RyanCavanaugh commented on Mar 2, 2019

    @RyanCavanaugh
    Member

    What semantics are honestly going to change?

    There are currently 29 open issues in the proposal repo, any one of which represents possible semantic changes, plus any new changes the committee might come up with.

    From tc39/proposal-optional-chaining#2 / tc39/proposal-optional-chaining#10

    • Is undefined?.a.b an exception or undefined ?
    • Is (undefined?.a).b an exception or undefined ?
    • Is undefined?.a() an exception or undefined ?
    • What's behavior of a?.b() when b is undefined?

    From tc39/proposal-optional-chaining#5

    • What's the syntax?

    From tc39/proposal-optional-chaining#28

    • How, if at all, does it work with private fields?

    From tc39/proposal-optional-chaining#40

    • How does it work with the delete operator?

    tc39/proposal-optional-chaining#51

    • 65 comments and hundreds of votes on something like a dozen different syntax options, including a clarification that the "obvious" solution is "not available"

    From tc39/proposal-optional-chaining#61

    • Wait, is it a.?b or a?.b ?

    From tc39/proposal-optional-chaining#69

    • Will null?.prop result in undefined or null ?
  16. dusave commented on Mar 2, 2019

    @dusave

    I completely concur with Ryan Cavanaugh (@RyanCavanaugh)'s assessment. We've come a long way and we're closer than ever, but part of the reason it's been such a long running proposal is the complexity around JavaScript, the semantics that exist and need to continue to work, and the readability/understandability of the code proposed.

    There are currently 29 open issues in the proposal repo...

    One of the first things I plan to do once we reach Stage 2 is to go and clean out issues. As a semi-OCD person, this is going to scratch an itch I've had for awhile 😆. We simply didn't want to shut down discussions until we were a bit firmer in our approach, and Stage 2 will be that point.

    I can also answer some of the above questions:

    1. What's the syntax?
    • The syntax that we've largely reached consensus on (which will become official once it reaches Stage 2) is:
      • a?.b?.c for static property lookup
      • a?.[b] for dynamic property lookup
      • a?.() for optional function execution
      • delete a?.b for optional deletion
    • Not everyone is thrilled about these (trust me, that's part of why this proposal has taken as long as it has), but believe me, these are the best options that we have. If you've thought of something else, feel free to search in the issues because it's more than likely been discussed.
    1. How, if at all, does it work with private fields?
    • This one isn't baked into the proposal yet, simply because private fields themselves aren't baked yet. So we don't want to hold up this proposal if that one happens to stall out. Once that one has reached Stage 4, we will address it then.
    1. How does it work with the delete operator?
    • See Answer 1
    1. Will null?.prop result in undefined or null ?
    • This will result in undefined because by that logic, you are looking for prop, which is not defined. You can be more verbose in your code if you care about getting the null.

    I know most if not all of you are as excited as I am for this proposal. It's been a long road and I ask you wait just a little longer. These things take time, and since JavaScript is forever (once a proposal reaches Stage 4 and is officially part of the language, it can't be removed save for a security flaw), we want to make sure we've covered all the angles and considered all possibilities.

  17. wintercounter commented on Mar 2, 2019

    @wintercounter

    Maybe this is the wrong place to discuss the proposal but why it simply couldn't be ?foo.bar.baz.buz

  18. RyanCavanaugh commented on Mar 4, 2019

    @RyanCavanaugh
    Member

    This is the wrong place to discuss the proposal 😉

  19. nodkz commented on Mar 4, 2019

    @nodkz
    Author

    @dustinsavery will be great if Optional Chaining will be split into several parts (if possible).

    For example static property access may become Stage-3 or even more Stage-4, this gives legitimacy to TS team for implementing this feature in near time.

    And other non-cozy parts like dynamic lookup, function execution, delete operator – may stay at Stage-1 or 2.

    PS. Divide and conquer complex and loooong parts. 😉

  20. dusave commented on Mar 4, 2019

    @dusave

    Paul Damnhorn (@nodkz) Trust me when I say we have tried many different angles to get this through. The fact that it hasn’t advanced isn’t for lack of trying or brainstorming new ideas or approaches. This is one that I tried during our last meeting, but that was voted down. We have a plan that has buyoff, now we just require patience from the community.

  21. typescript-bot commented on Mar 6, 2019

    @typescript-bot
    Contributor

    This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  22. nodkz commented on Mar 7, 2019

    @nodkz
    Author

    TypeScript Bot (@typescript-bot) holy life, you will close issues from us??? And we – the bags of blood and bones are nothing for you... Realy???

    Ryan Cavanaugh (@RyanCavanaugh) @dustinsavery please alive this issue! and remove duplicate to relax the typescripts's bot.

    I understand that complex questions better to keep closed by the bot... But it's too early to allow bots to take decisions in 2019...

    #diversity #humanity_please_live #no_bots

  23. locked as resolved and limited conversation to collaborators on Mar 7, 2019
  24. RyanCavanaugh commented on Mar 7, 2019

    @RyanCavanaugh
    Member

    As a human, I approve the bot's behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions