Functions
Functions have two places where types are applied: parameters (input) and the return value (output).
1function concat(a: string, b: string): string {2 return a + b;3}4
5concat("foo", "bar"); // Works!6concat(true, false); // Error!
6:8-6:11: Cannot call `concat` with `true` bound to `a` because boolean [1] is incompatible with string [2]. [incompatible-call]6:14-6:18: Cannot call `concat` with `false` bound to `b` because boolean [1] is incompatible with string [2]. [incompatible-call]
Using inference, return types are often optional:
1function concat(a: string, b: string) {2 return a + b;3}4
5const s: string = concat("foo", "bar"); // Works!
If defined where we can get the type from the context of the expression, type annotations can be optional:
1[1, 2, 3].map(x => x * x); // From the context, we know parameter `x` has type `number`
Syntax of functions
There are three forms of functions that each have their own slightly different syntax.
Function Declarations
1function func(str: string, bool?: boolean, ...nums: Array<number>): void {2 // ...3}
Arrow Functions
1let func = (str: string, bool?: boolean, ...nums: Array<number>): void => {2 // ...3};
Function Types
1type T = (str: string, bool?: boolean, ...nums: Array<number>) => void;
You may also optionally leave out the parameter names.
1type T = (string, boolean | void, Array<number>) => void;
You might use these functions types for something like a callback.
1function func(callback: (error: Error | null, value: string | null) => void) {2 // ...3}
Type arguments
Functions can have type arguments:
1function f<T>(x: T): Array<T> {2 return [x];3}4
5const g = <T>(x: T): Array<T> => [x];6
7type H = <T>(T) => Array<T>;
Function Parameters
Function parameters can have types by adding a colon :
followed by the type
after the name of the parameter.
1function func(param1: string, param2: boolean) {2 // ...3}
Optional Parameters
You can also have optional parameters by adding a question mark ?
after the
name of the parameter and before the colon :
.
1function func(optionalValue?: string) {2 // ...3}
Optional parameters will accept missing, undefined
, or matching types. But
they will not accept null
.
1function func(optionalValue?: string) {2 // ...3}4
5func(); // Works.6func(undefined); // Works.7func("string"); // Works.8
9func(null); // Error!
9:6-9:9: Cannot call `func` with `null` bound to `optionalValue` because null [1] is incompatible with string [2]. [incompatible-call]
Rest Parameters
JavaScript also supports having rest parameters or parameters that collect an
array of arguments at the end of a list of parameters. These have an ellipsis
...
before them.
You can also add type annotations for rest parameters using the same syntax but
with an Array
.
1function func(...args: Array<number>) {2 // ...3}
You can pass as many arguments as you want into a rest parameter.
1function func(...args: Array<number>) {2 // ...3}4
5func(); // Works.6func(1); // Works.7func(1, 2); // Works.8func(1, 2, 3); // Works.
Note: If you add a type annotation to a rest parameter, it must always explicitly be an
Array
of$ReadOnlyArray
type.
this
parameter
Every function in JavaScript can be called with a special context named this
.
You can call a function with any context that you want. Flow allows you to annotate
the type for this context by adding a special parameter at the start of the function's parameter list:
1function func<T>(this: { x: T }) : T {2 return this.x;3}4
5const num: number = func.call({x : 42});6const str: string = func.call({x : 42}); // Error!
6:21-6:39: Cannot assign `func.call(...)` to `str` because number [1] is incompatible with string [2]. [incompatible-type]
This parameter has no effect at runtime, and is erased along with types when Flow is transformed into JavaScript.
When present, this
parameters must always appear at the very beginning of the function's parameter list, and must
have an annotation. Additionally, arrow functions may not have a this
parameter annotation, as
these functions bind their this
parameter at the definition site, rather than the call site.
If an explicit this
parameter is not provided, Flow will attempt to infer one based on usage. If this
is not mentioned
in the body of the function, Flow will infer mixed
for its this
parameter.
Function Returns
Function returns can also add a type using a colon :
followed by the type
after the list of parameters.
1function func(): number {2 return 1;3}
Return types ensure that every branch of your function returns the same type. This prevents you from accidentally not returning a value under certain conditions.
1function func(): boolean { 2 if (Math.random() > 0.5) {3 return true;4 }5}
1:18-1:24: Cannot expect boolean as the return type of function because boolean [1] is incompatible with implicitly-returned undefined. [incompatible-return]
Async functions implicitly return a promise, so the return type must always be a Promise
.
1async function func(): Promise<number> {2 return 123;3}
Predicate Functions
Predicate functions are deprecated and will be removed in a future version. Use type guards instead.
Sometimes you will want to move the condition from an if
statement into a function:
1function concat(a: ?string, b: ?string): string {2 if (a != null && b != null) {3 return a + b;4 }5 return '';6}
However, Flow will error in the code below:
1function truthy(a: ?string, b: ?string): boolean {2 return a != null && b != null;3}4
5function concat(a: ?string, b: ?string): string {6 if (truthy(a, b)) {7 return a + b; // Error! 8 }9 return '';10}
7:12-7:16: Cannot use operator `+` with operands null or undefined [1] and null or undefined [2] [unsafe-addition]7:12-7:16: Cannot use operator `+` with operands null or undefined [1] and string [2] [unsafe-addition]7:12-7:16: Cannot use operator `+` with operands string [1] and null or undefined [2] [unsafe-addition]
This is because the refinement information of a
and b
as string
instead of ?string
is lost when returning from the truthy
function.
You can fix this by making truthy
a predicate function, by using the %checks
annotation like so:
1function truthy(a: ?string, b: ?string): boolean %checks { 2 return a != null && b != null;3}4
5function concat(a: ?string, b: ?string): string {6 if (truthy(a, b)) {7 return a + b; 8 }9 return '';10}
1:50-1:56: Support for predicate functions is removed. `%checks` declaration is now ignored. [unsupported-syntax]7:12-7:16: Cannot use operator `+` with operands null or undefined [1] and null or undefined [2] [unsafe-addition]7:12-7:16: Cannot use operator `+` with operands null or undefined [1] and string [2] [unsafe-addition]7:12-7:16: Cannot use operator `+` with operands string [1] and null or undefined [2] [unsafe-addition]
Limitations of predicate functions
The body of these predicate functions need to be expressions (i.e. local variable declarations are not supported). But it's possible to call other predicate functions inside a predicate function. For example:
1function isString(y: mixed): %checks { 2 return typeof y === "string";3}4
5function isNumber(y: mixed): %checks { 6 return typeof y === "number";7}8
9function isNumberOrString(y: mixed): %checks { 10 return isString(y) || isNumber(y);11}12
13function foo(x: string | number | Array<mixed>): string | number {14 if (isNumberOrString(x)) {15 return x + x; 16 } else {17 return x.length; // no error, because Flow infers that x can only be an array 18 }19}20
21foo('a');22foo(5);23foo([]);
1:28-1:36: Support for predicate functions is removed. `%checks` declaration is now ignored. [unsupported-syntax]5:28-5:36: Support for predicate functions is removed. `%checks` declaration is now ignored. [unsupported-syntax]9:36-9:44: Support for predicate functions is removed. `%checks` declaration is now ignored. [unsupported-syntax]15:12-15:16: Cannot use operator `+` with operands string [1] and array type [2] [unsafe-addition]15:12-15:16: Cannot use operator `+` with operands number [1] and array type [2] [unsafe-addition]15:12-15:16: Cannot use operator `+` with operands array type [1] and string [2] [unsafe-addition]15:12-15:16: Cannot use operator `+` with operands array type [1] and number [2] [unsafe-addition]15:12-15:16: Cannot use operator `+` with operands array type [1] and array type [1] [unsafe-addition]17:14-17:19: Cannot get `x.length` because property `length` is missing in `Number` [1]. [prop-missing]
Another limitation is on the range of predicates that can be encoded. The refinements that are supported in a predicate function must refer directly to the value that is passed in as an argument to the respective call.
For example, consider the inlined refinement
1declare const obj: {n?: number};2
3if (obj.n != null) {4 const n: number = obj.n;5}
Here, Flow will let you refine obj.n
from ?number
to number
. Note that the
refinement here is on the property n
of obj
, rather than obj
itself.
If you tried to create a predicate function to encode the same condition, then the following refinement would fail
1function bar(a: {n?: number, ...}): %checks { 2 return a.n != null;3}4
5declare const obj: {n?: number};6
7if (bar(obj)) {8 const n: number = obj.n; // Error 9}
1:35-1:43: Support for predicate functions is removed. `%checks` declaration is now ignored. [unsupported-syntax]8:21-8:25: Cannot assign `obj.n` to `n` because undefined [1] is incompatible with number [2]. [incompatible-type]
This is because the only refinements supported through bar
would be on obj
itself.
Callable Objects
Callable objects can be typed, for example:
1type CallableObj = {2 (number, number): number,3 bar: string,4 ...5};6
7function add(x: number, y: number) {8 return x + y;9}10
11add.bar = "hello world";12
13add as CallableObj;
In general, functions can have properties assigned to them if they are function declarations, or
simple variable declarations of the form const f = () => ...
. The properties must be assigned in
the format f.prop = <expr>;
, in the same statement list as the function definition (i.e. not conditionally).
Note that the object representing the static properties assigned to the function is inexact.
Overloaded functions
You can use intersection types to define overloaded function types:
1declare const fn:2 & ((x: 'string') => string)3 & ((x: 'number') => number)4
5const s: string = fn('string');6const n: number = fn('number');
Any function
If you want to specify you want to allow any function, and do not care what it is, you can use this pattern:
1function useCallback<T: (...$ReadOnlyArray<empty>) => mixed>(2 callback: T,3 inputs: ?$ReadOnlyArray<mixed>,4): T {5 return callback;6}7useCallback((x: string) => true); // OK8useCallback((x: number) => [1]); // OK
You could use type arguments to capture the arguments and return type, to do more complicated transformations:
1function func<TArgs: $ReadOnlyArray<mixed>, TReturn>(2 callback: (...TArgs) => TReturn,3): (boolean, ...TArgs) => Array<TReturn> {4 return (b, ...args): Array<TReturn> => {5 if (b) {6 return [callback(...args)];7 } else {8 return [];9 }10 };11}12
13const f: (boolean, string, number) => Array<string> =14 func((x: string, y: number) => x.slice(y)); // OK
The type Function
is just an alias for any
, and is unsafe.
You can ban its use in your code with the unclear-type lint.