0

I have the following setup:

const foo = async <T>(a): Promise<T> => {
  return await a // call server here
}

type A = { bar: 'bar' } | { baz: 'baz' }
foo<A>({ bar: 'bar' })
  .then(response => {
    // error: property bar doesn't exist on type { baz: 'baz' }
    if (response.bar) console.log('bar') 

    // error: property baz doesn't exist on type { bar: 'bar' }
    if (response.baz) console.log('baz')
  })

Is this a bug in typescript or my setup is wrong?

5
  • That seems to be working just fine, what did you expect to happen? I'd suggest reading e.g. typescriptlang.org/docs/handbook/2/narrowing.html, the promise and generic type aren't relevant here. Commented May 31, 2021 at 15:53
  • Why would this be related to generics? Or be a bug? Or somehow wrong? Here is code without generics that works the same way. It works as it should - properties from the union that don't overlap cannot be addressed directly. What made you expect the opposite? Commented May 31, 2021 at 15:57
  • 2
    if ('bar' in response) console.log('bar') Commented May 31, 2021 at 16:25
  • @Keith if you make an answer out of your comment with a bit more detail I'll accept. thanks! Commented Jun 3, 2021 at 10:47
  • 1
    @goldylucks Ok, done. Hopefully my explanation makes sense. Commented Jun 3, 2021 at 13:18

2 Answers 2

1

Unfortunately Typescript doesn't narrow types down when doing a simple truefy test on a property.. if (foo.bar) {}. I believe this is because properties in JavaScript can be dynamic, eg. property getters / proxy's etc / return 0 / false / null etc. IOW: if (obj.prop) has more meanings than does this property exist.

Luckily JavaScript does have a special keyword called in, that specifically checks if a property exists on an object.

So if you do if ('bar' in foo), Typescript can be pretty certain that it can now narrow down the type for bar.

So in your example

if ('bar' in response) console.log('bar') 

will not error, but better still response will now also have a property called bar.

if (`bar` in response) console.log(response.bar)

In a good editor, when typing response inside that console.log( the editor will also give bar as an option.

Sign up to request clarification or add additional context in comments.

Comments

0

You can override this situation with type assertion :

const foo = async <T>(a): Promise<T> => {
    return await a // call server here
}

type Bar = { bar: 'bar' }
type Baz = { baz: 'baz' }
type A = Bar | Baz

foo<A>({ bar: 'bar' })
    .then(response => {
        if ((response as Bar).bar) console.log('bar')
        if ((response as Baz).baz) console.log('baz')
    })

But I really do not recommend it, if you are abstracting this from more complex example here what I suggest you to do:

type Bar = { type: 'bar', bar: 'bar' }
type Baz = { type: 'baz', baz: 'baz' }
type A = Bar | Baz

foo<A>({ bar: 'bar' })
    .then(response => {
        switch (response.type) {
            case 'bar':
                console.log(response.bar)
                break;
            case 'baz':
                console.log(response.baz)
                break;
            default:
                console.log('unknown type')
        }
    })

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.