
7 Day Free Trial. Cancel Anytime.
How often have you found yourself tracking down a bug in some code, only to find the error was something simple that should have been avoidable? Maybe you passed the arguments to a function in the wrong order, or perhaps you tried to pass a string instead of a number? JavaScript’s weak typing system and willingness to try to coerce variables into different types can be a source of a whole class of bugs that just don’t exist in statically typed languages.
March 30th, 2017: The article was updated to reflect changes to the Flow library.
Flow is a static type checker for JavaScript first introduced by Facebook at theScale Conference in 2014. It was conceived with a goal of finding type errors in JavaScript code, often without having to modify our actual code, hence consuming little effort from the programmer. At the same time, it also adds additional syntax to JavaScript that provides more control to the developers.
In this article, I’ll introduce you to Flow and it’s main features. We’ll look at how to set it up, how to add type annotations to your code, and how to automatically strip out those annotations when running the code.
Flow currently works on Mac OS X, Linux (64-bit) and Windows (64-bit). The easiest way to install it is via npm:
npm install --save-dev flow-binand add it to your project’spackage.json file, under thescripts section:
"scripts": { "flow": "flow"}Once this is done, we’re ready to go ahead and explore its features.
A configuration file named.flowconfig must be present at the root of the project folder. We can create an empty config file by running the command:
npm run flow initOnce the config file is present, you can run ad-hoc checks on the code within your project folder and any subfolders by running the following command at the terminal:
npm run flow checkHowever, this is not the most efficient way to use Flow since it causes Flow itself to recheck the entire project’s file structure every time. We can use the Flow server, instead.
The Flow server checks the file incrementally which means that it only checks the part that has changed. The server can be started by running on the terminal the commandnpm run flow.
The first time you run this command, the server will start and show the initial test results. This allows for a much faster and incremental workflow. Every time you want to know the test results, runflow on the terminal. After you’re done with your coding session, you can stop the server usingnpm run flow stop.
Flow’s type checking isopt-in. This means that you don’t need to check all your code at once. You can select the files you want to check and Flow will do the job for you. This selection is done by adding@flow as a comment at the top of any JavaScript files you want to be checked by Flow:
/*@flow*/This helps a lot when you’re trying to integrate Flow into an existing project as you can choose the files that you want to check one by one and resolve any errors.
Generally, type checking can be done in two ways:
With annotations, we have to write some extra code which is only useful during development and is stripped off from the final JavaScript build that will be loaded by the browser. This requires a bit of extra work upfront to make the code checkable by adding those extra type annotations.
In the second case, the code is already ready for being tested without any modification, hence minimizing the programmer’s effort. It doesn’t force you to change how you code as it automatically deduces the data type of the expressions. This is known astype inference and is one of the most important features of Flow.
To illustrate this feature, we can take the below code as an example:
/*@flow*/function foo(x) { return x.split(' ');}foo(34);This code will give an error on the terminal when you run thenpm run flow command, as the functionfoo() expects a string while we have passed a number as an argument.
The error will look something like this:
index.js:4 4: return x.split(' '); ^^^^^ property `split`. Property not found in 4: return x.split(' '); ^ NumberIt clearly states the location and the cause of the error. As soon as we change the argument from a number to any string, as shown in the following snippet, the error will disappear.
/*@flow*/function foo(x) { return x.split(' ');};foo('Hello World!');As I said, the above code won’t give any errors. What we can see here is that Flow understands that thesplit() method is only applicable to astring, so it expectsx to be astring.
Flow treatsnull in a different way compared to other type systems. It doesn’t ignorenull, thus it prevents errors that may crash the application wherenull is passed instead of some other valid types.
Consider the following code:
/*@flow*/function stringLength (str) { return str.length;}var length = stringLength(null);In the above case, Flow will throw an error. To fix this, we’ll have to handlenull separately as shown below:
/*@flow*/function stringLength (str) { if (str !== null) { return str.length; } return 0;}var length = stringLength(null);We introduce a check fornull to ensure that the code works correctly in all cases. Flow will consider this last snippet as a valid code.
As I mentioned above, type inference is one of the best features of Flow, as we can get useful feedback without having to write type annotations. However, in some cases, adding annotations to the code is necessary to provide better checking and remove ambiguity.
Consider the following:
/*@flow*/function foo(x, y){ return x + y;}foo('Hello', 42);Flow won’t find any errors in the above code because the+ (plus) operator can be used on strings and numbers, and we didn’t specify that the parameters ofadd() must be numbers.
In this case, we can use type annotations to specify the desired behavior. Type annotations are prefixed with a: (colon) and can be placed on function parameters, return types, and variable declarations.
If we add type annotations to the above code, it becomes as reported below:
/*@flow*/function foo(x : number, y : number) : number { return x + y;}foo('Hello', 42);This code shows an error because the function expects numbers as arguments while we’re providing a string.
The error shown on the terminal will look like the following:
index.js:7 7: foo('Hello', 42); ^^^^^^^ string. This type is incompatible with the expected param type of 3: function foo(x : number, y : number) : number{ ^^^^^^ numberIf we pass a number instead of'Hello', there won’t be any error. Type annotations are also useful in large and complex JavaScript files to specify the desired behavior.
With the previous example in mind, let’s have a look at the various other type annotations supported by Flow.
/*@flow*//*--------- Type annotating a function --------*/function add(x : number, y : number) : number { return x + y;}add(3, 4);The above code shows the annotation of a variable and a function. The arguments of theadd() function, as well as the value returned, are expected to be numbers. If we pass any other data type, Flow will throw an error.
/*-------- Type annotating an array ----------*/var foo : Array<number> = [1,2,3];Array annotations are in the form ofArray<T> whereT denotes the data type of individual elements of the array. In the above code,foo is an array whose elements should be numbers.
An example schema of class and object is given below. The only aspect to keep in mind is that we can perform an OR operation among two types using the| symbol. The variablebar1 is annotated with respect to the schema of theBar class.
/*-------- Type annotating a Class ---------*/class Bar{ x:string; // x should be string y:string | number; // y can be either a string or a number constructor(x,y){ this.x=x; this.y=y; }}var bar1 : Bar = new Bar("hello",4);We can annotate object literals in a similar way to classes, specifying the types of the object’s properties.
/*--------- Type annonating an object ---------*/var obj : {a : string, b : number, c: Array<string>, d : Bar} = { a : "hello", b : 42, c : ["hello", "world"], d : new Bar("hello",3)}Any typeT can be made to includenull/undefined by writing?T instead ofT as shown below:
/*@flow*/var foo : ?string = null;In this case,foo can be either a string ornull.
We’re just scratching the surface of Flow’s type annotation system here. Once you get comfortable with using these basic types, I suggest delving intothe types documentation on Flow’s website.
We often face situations where we have to use methods from third-party libraries in our code. Flow will throw an error in this case but, usually, we don’t want to see those errors as they will distract from checking our own code.
Thankfully, we don’t need to touch the library code to prevent these errors. Instead, we can create a library definition (libdef). A libdef is just a fancy term for a JavaScript file that contains declarations of the functions or the methods provided by the third-party code.
Let’s see an example to better understand what we’re discussing:
/* @flow */var users = [ { name: 'John', designation: 'developer' }, { name: 'Doe', designation: 'designer' }];function getDeveloper() { return _.findWhere(users, {designation: 'developer'});}This code will give the following error:
interfaces/app.js:9 9: return _.findWhere(users, {designation: 'developer'}); ^ identifier `_`. Could not resolve nameThe error is generated because Flow doesn’t know anything about the_ variable. To fix this issue we need to bring in a libdef for Underscore.
Thankfully, there is a repository calledflow-typed which contains libdef files for many popular third-party libraries. To use them, you simply need to download the relevant definition into a folder namedflow-typed within the root of your project.
To streamline the process even further, there is a command line tool available for fetching and installing libdef files. It’s installed via npm:
npm install -g flow-typedOnce installed, runningflow-typed install will examine your project’spackage.json file and download libdefs for any dependencies it finds.
If the library you’re using doesn’t have a libdef available in the flow-typed repository, it’s possible to create your own. I won’t go into details here, as it’s something you shouldn’t need to do very often, but if you’re interested you can check outthe documentation.
As type annotations are not valid JavaScript syntax, we need to strip them from the code before executing it in the browser. This can be done usingthe flow-remove-types tool or as aBabel preset, if you’re already using Babel to transpile your code. We’ll only discuss the first method in this article.
First, we need to install flow-remove-types as a project dependency:
npm install --save-dev flow-remove-typesThen we can add anotherscript entry to ourpackage.json file:
"scripts": { "flow": "flow", "build": "flow-remove-types src/ -D dest/",}This command will strip all the type annotations from the files present in thesrc folder and store the compiled version in thedist folder. The compiled files can be loaded on the browser just like any other JavaScript file.
There are plugins available forseveral module bundlers to strip annotations as part of the build process.
In this article, we discussed the various type checking features of Flow and how they can help us catch errors and improve the quality of our code. We also saw how Flow makes it very easy to get started by ‘opting in’ on a per-file basis, and doing type inference so we can start getting useful feedback without having to add annotations throughout our code,
How do you feel about static type checking for JavaScript? Is this something you can see being useful, or just another unnecessary tool bringing more complexity to modern JavaScript? Has this article encouraged you to check out Flow for yourself? Feel free to share your thoughts, doubts, or comments below.
Flow is a static type checker for JavaScript, developed by Facebook. It helps you catch errors in your code before they happen, making your code more robust and maintainable. Flow does this by analyzing your code and inferring types where it can, and using annotations where it can’t. This allows it to catch common errors like null pointer exceptions, incorrect function usage, and more. It’s a powerful tool that can greatly improve your JavaScript coding.
Installing Flow is straightforward. You can install it globally using npm with the commandnpm install -g flow-bin. To set it up in your project, you need to initialize it with the commandflow init. This will create a.flowconfig file in your project root. You can then annotate your JavaScript files with/* @flow */ to enable Flow type checking for that file.
Once you’ve set up Flow in your project, you can use it to check types in your JavaScript code. You do this by adding type annotations to your variables, function parameters, and return values. For example,function square(n: number): number { return n * n; } declares a functionsquare that takes a number and returns a number. You can then runflow in your terminal to check your code for type errors.
While both Flow and TypeScript offer static type checking for JavaScript, they have some key differences. Flow is more flexible in its type system, allowing for more fine-grained control over your types. It also integrates better with existing JavaScript code, making it easier to gradually adopt in your project. However, TypeScript has better tooling and community support, so the best choice depends on your specific needs.
Flow has built-in support for null and undefined values. By default, it does not allow you to use a value that could be null or undefined without first checking if it is. This helps prevent null pointer exceptions. You can check for null or undefined values using theif (value != null) syntax.
Yes, Flow works well with React and many other JavaScript libraries. It has built-in support for React, allowing you to type check your components, props, and state. For other libraries, you can use library definitions from the flow-typed repository or write your own.
Flow works well with Babel and other JavaScript transpilers. You can use thebabel-plugin-transform-flow-strip-types plugin to strip Flow type annotations from your code before it is transpiled. This allows you to use Flow in your development process without affecting your production code.
Flow can help you catch a wide range of common errors in your JavaScript code. These include null pointer exceptions, incorrect function usage, type mismatches, and more. By catching these errors before your code runs, Flow can help you write more robust and maintainable code.
Flow has a powerful type system that can handle complex types. You can define custom types using thetype keyword, and you can use union and intersection types to combine existing types. You can also use generics to create reusable, type-safe code.
Migrating an existing JavaScript project to use Flow can be done gradually. You can start by initializing Flow in your project and adding the/* @flow */ annotation to a few files. You can then gradually add type annotations to your code and enable Flow in more files as you go. This allows you to start benefiting from Flow’s type checking without having to convert your entire codebase at once.
7 Day Free Trial. Cancel Anytime.