akonsu akonsu - 3 months ago 9
Javascript Question

flow: check type of thenable

I have a function that accepts either a thenable (an object that has a

then()
method, see at the top of https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve) or something else:

function resolve<T>(value: {then: ()=>T}|T) {
if (value && value.then) {
console.log('thenable', value.then);
} else {
console.log('not thenable');
}
}


Flow complains when I access
value.then
in this
if
statement. I can fix it with
(value: any).then
but this looks hacky.

Can anyone recommend a good way to type check this?

https://tryflow.org/?code=ZnVuY3Rpb24gcmVzb2x2ZTxUPih2YWx1ZToge3RoZW46ICgpPT5UfXxUKSB7CiAgICBpZiAodmFsdWUgJiYgdmFsdWUudGhlbikgewogICAgICAgIGNvbnNvbGUubG9nKCd0aGVuYWJsZScsIHZhbHVlLnRoZW4pOwogICAgfSBlbHNlIHsKICAgICAgICBjb25zb2xlLmxvZygnbm90IHRoZW5hYmxlJyk7CiAgICB9Cn0=

Answer

Great question! This is something that the Flow team has been wrestling with over the last few weeks!

What is the problem

if (value && value.then) {
  // What is the type of `value` here?
} else

Inside of that if statement, what is the type of value? If T is string, then it will be {then: ()=>T}, as you expect. But what if T is { then: string }? For all we know, T might have a property named then!

What is the Flow team doing to fix this?

  • Adding exact object types. This problem comes from not knowing whether one branch of a union may or may not have a property. With exact types, you can tell Flow exactly which properties an object has.
  • Allowing the value.then property check, and refining the type of value.then to mixed.

A lot of this work is already in master. You can check out flowtype.org/try, which currently runs off of master. Your example on flowtype.org/try

Once this stuff lands (some comes in v0.31.0 and some in v0.32.0) we'll document and blog about it.

edit: adding more information

3 main problems

There are 3 general questions that we're working on resolving.

  • When should we allow value.then in a conditional? If we only allow value.then when we're sure that value has a then property, then we can catch typos like value.tehn. However, idiomatic JavaScript often tests objects for properties that may or may not exist.
  • What is the type of value if the conditional is true or false. In the provided example, value is a union type. It appears like the intention of value && value.then is to detect if the function is using the left branch of the union. However, Flow can't safely choose a branch, since T might have a then field.
  • What is the type of value.then if the conditional is true or false. Again, T might be an object like { then: string }, so value.then might be anything

Our solutions

When should we allow value.then in a conditional

We'll always allow value.then. This means we can't catch property name typos as easily, but it means we can support more idiomatic JavaScript. One of Flow's main tenets is that it works well with the JavaScript that people tend to write.

What is the type of value if the conditional is true or false.

If Flow knows for certain that only one branch of the union type will work, it will refine the type of value to that branch. Otherwise, value won't be refined. Exact types will help with this

What is the type of value.then if the conditional is true or false

If Flow knows for certain that only one branch of the union type will work, it will refine the type of value.then to the type of the then property on that branch. If Flow knows for certain that no branch has that property, it will error. Otherwise, it will use the type mixed. Exact types help with this too.

What are exact types

{ x: string } is the type of an object with a property x which has the type string.

var example1: { x: string } = { x: 'hello' }; // This is ok
var example2: { x: string } = { x: 'hello', y: 123 }; // This is also ok

This is useful for idiomatic JavaScript, but makes it hard for Flow to say that an object type DOESN'T have a property. So we're adding exact types.

{| x: string |} is the type of an object with a property x which has the type string but no other properties.

var example1: {| x: string |} = { x: 'hello' }; // This is ok
var example2: {| x: string |} = { x: 'hello', y: 123 }; // Error! Extra property y!

This helps, because you can write something like this:

type Foo = {| x: string |} | {| y: string |};

function test(arg: Foo): string | void {
  if (arg.x) {
    return arg.x;
  }
}

Once we launch these, we'll document them! So keep your eyes peeled!

Comments