Flow Cookbook

Gotchas and interesting cases of flow issues
Gotchas and interesting cases of flow issues
This is a platform for User Generated Content. G/O Media assumes no liability for content posted by Kinja users to this platform.

Type Refinement Invalidation

Lately a couple of backend developers started touching javascript code and more of them ran into the issue of type refinement invalidation. It’s probably something that doesn’t really happen in Scala, but someone can enlighten me about it. This is what happens:

Advertisement

Flow will complain here that it can’t call charAt(0) on undefined. And it turns out, flow is right!

When you check the existence of a variable / optional parameter, calling any function before using it invalidates that check, and you need to check again. This is because otherMethod can potentially modify the value you checked previously. You might think it shouldn’t be able to, since we didn’t pass `value` as an argument, but here is an example, where it actually does modify it:

Advertisement

Since javascript functions are not without side effect and can modify global values (and indeed values in the scope), it is perfectly reasonable for flow to throw an error. The solution is to move the value under examination into a const inside method. That means that otherMethod has no access to it, and is not able to modify it:

Advertisement

The above examples are fairly easy to catch for the trained eye. There are some more intricate examples, however:

Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ packages/kinja-components/components/impact-header/read-only/impact-header-video.js:72:70

Cannot call this.props.createVideoPlayer because undefined [1] is not a function.

[1] 56│ createVideoPlayer?: (nodeOrId: HTMLElement | string, config: JWPlayerConfig) => Promise<JWPlayer>
:
69│ fetchVideoMetadata(this.props.video.id).then(video => {
70│ window.requestAnimationFrame(() => {
71│ // const posterImage = ;
72│ this.videoElement && this.props.createVideoPlayer && this.props.createVideoPlayer(this.videoElement, {
73│ playlist: [{
74│ file: video.signedPlaylistUrl,
75│ image: imageUrl(video.poster.id, 'KinjaUncroppedLargeAuto') || '',
76│ title: video.title,
77│ mediaid: video.jwpId,
78│ mcpId: video.mcpId,
79│ property: video.property
80│ }],
Advertisement

On line 72, we are checking for the existence of createVideoPlayer, before immediately calling it. So what’s the issue? It’s actually the same as before, calling a function between checking the existence of and using the value. On line 75 we are calling imageUrl, and because of how javascript is evaluated, that call happens before the call of createVideoPlayer itself.

Note that typescript doesn’t catch this issue. Typescript does sacrifice some amount of type safety for ease of use. You can form your own opinion if that’s good or bad, I’m on flow’s side in this case.

Advertisement

You can learn more about type refinement invalidations in the official docs.

The Marciverse awaits

Share This Story

Get our newsletter