I have a function that accepts either a thenable (an object that has a then()
method; see the top of MDN JavaScript docs: 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');
}
}
Try Flow demo
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?
Great question! This is something that the Flow team has been wrestling with over the last few weeks!
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
!
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
There are 3 general questions that we're working on resolving.
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.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.value.then
if the conditional is true or false. Again, T
might be an object like { then: string }
, so value.then
might be anythingWhen 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.
{ 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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With