Skip to main content

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

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}

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 array18  }19}20
21foo('a');22foo(5);23foo([]);

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}
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.