How to Write Better JavaScript with Flow: a Simple Guide
Why Use Flow?
Developers and support engineers are spending thousands of work hours debugging code. Even the most straightforward bugs like passing incorrect data types such as passing ‘2’ instead of 2 (i.e., string instead of a number) or incorrectly passing arguments to a function could break a real-time application. The worst part is that debugging such a simple bug in thousands of lines of code distributed across several tightly coupled files could take hours. A developer could insert several bugs in his code without realizing it, especially in a dynamically typed language like JavaScript, with its weak typing system and the JavaScript type coercion.
That’s why it helps to learn some practical ways to write better JavaScript. Using TypeScript or implementing modern JavaScript features like async, await, or arrow functions helps. But still, the very first step should be to start with the fundamental: the data types.
While coercing variables into different types, we can end up with a whole class of bugs in JavaScript that don’t exist in statically typed languages like C, C++, Go, Scala, Rust, and Java.
The main advantage of a statically typed language is that the compiler handles all kinds of checks, and therefore a lot of trivial bugs are caught at a very early stage.
To achieve this in JavaScript, Facebook has introduced Flow – a static type checker for JavaScript. Flow handles type errors effectively and very quickly without changing the actual code. It helps developers code faster and smarter. With Flow, a developer can feel more confident about his code. Flow checks the code for errors through static type annotations.
In this article, we will go through Flow and its uses. We will start by setting up Flow, and we will see how Flow helps us code in JavaScript.
Setting Up Flow
Before installing Flow, first, we need to set up a compiler. You can use any of the flow-remove-types or Babel to work with Flow. In this guide, we will use Babel: it will strip out all types of annotations from the Flow code.
npm install --save-dev @babel/core @babel/cli @babel/preset-flow
npm install --save-dev flow-bin
"flow"
script to your package.json
{
"name": "my-flow-project",
"version": "1.0.0",
"devDependencies": {
"flow-bin": "^0.178.0"
},
"scripts": {
"flow": "flow"
}
}
Running Flow
init
. This will generate a configuration file in the project folder and name it as .flowconfig
npm run flow init
npm run flow check
Even though it works fine, there is a downside: every time the developer adds new code or changes a file, Flow rechecks the entire project’s file structure. So it’s not the most efficient to use Flow like this. Instead, we can use the Flow server.
With the Flow server enabled, instead of checking the entire project folder, the Flow server only checks the part that has changed.
npm run flow.
- The server will start during the first run, and initial test results will be displayed. From the next run, well notice that the workflow will get incrementally faster.
- Whenever you want to know the test results, use the flow command in the terminal.
- Once you’re done coding for the day, do not forget to stop the server with
npm run flow stop
. - You can opt in specific files for type checking by adding comment
@flow
in the beginning of a JS file. This is extremely useful when working on a project with multiple JS files. You can decorate each file with @flow as a comment and debug one file at a time.
How Type Inference Works
We can do type checking in two ways. First one is to specify the expected data type in the code by using annotations. The type checker evaluates the code accordingly. The drawback is that writing annotations can be tedious; and this extra code is only applicable during development. It will get removed from the final JavaScript build at the time of loading by the browser. That’s why code inference is the preferred way of type checking according to most developers. In this case, we use an intelligent tool that can automatically infer the desired data type of a given variable by understanding the context in the code via code interference.
Besides, in this case, we are testing code without any modifications by saving a lot of time for developers . The Type inference is the most appreciated feature of Flow.
Lets looks at some real-world scenarios of using Flow.
Case 1
/*@flow*/
function foo(x) {
return x.split(' ');
}
foo(78);
In this case, function foo expects a string as an argument, but instead we just passed a number.
npm run flow
we can easily catch that mistake.index.js:4
4: return x.split(' ');
^^^^^ property `split`. Property not found in
4: return x.split(' ');
^ Number
We can clearly see the number of the line as well as the error. All that is left now is to proceed and modify the number to a string.
Case 2
Nullable Types
Flow doesn’t ignore null. It treats it differently compared to other types. So it catches all the errors where null is passed instead of some other valid data types.
/*@flow*/
function stringSlice(str) {
return str.slice(7);
}
var length = stringSlice(null);
/*@flow*/
function stringSlice (str) {
if (str !== null) {
return str.slice(7);
}
return “NA”;
}
var length = stringSlice(null);
Case 3
Library Definitions
We will be using a number of third-party libraries while coding. Flow will throw false alerts in this case, which can be distracting. Usually third party libraries are well tested, and end-users are not expected to touch them.
We can create a libdef
i.e a library definition to prevent this issue. It contains declarations of the methods / functions of the third-party code.
/* @flow */
var users = [
{ user: 'Joey', designation: 'Actor' },
{ user: 'Drake', designation: 'Doctor' }
];
function getDoctor() {
return _.findWhere(users, {designation: ''Doctor''});
}
interfaces/app.js:9
9: return _.findWhere(users, {designation: 'developer'});
^ identifier `_`. Could not resolve user
So to fix this issue we need to bring in a libdef for underscore. You can also use flow-typed which contains libdef files for most third-party libraries. Use npm install -g flow-typed
to set this up.
Conclusion
Now we know how to write better JavaScript with Flow. We have gone through the various type-checking features of Flow and how useful it is to improve the quality of our code.
If you’re also looking into how to improve your testing by streamlining the QA process and adopting a codeless test automation tool – feel free to check out testRigor by registering a free account. Our customers create tests up to 15x faster than other automation tools, and spend almost no time on test maintenance.