Functions, lambdas, and execution flow

The same as in JavaScript, functions are the processing machines where we analyze input, digest information, and apply the necessary transformations to the data provided to either transform the state of our application or return an output that will be used to shape our application's business logic or user interactivity.

Functions in TypeScript are not that different from regular JavaScript, except for the fact that functions, just as everything else in TypeScript, can be annotated with static types and thus, they better inform the compiler of the information they expect in their signature and the data type they aim to return, if any.

Annotating types in our functions

The following example showcases how a regular function is annotated in TypeScript:

function sayHello(name: string): string {
    return 'Hello, ' + name;
}

We can clearly see two main differences from the usual function syntax in regular JavaScript. First, we annotate with type information the parameters declared in the function signature. This makes sense since the compiler will want to check whether the data provided when executing the function holds the correct type. In addition to this, we also annotate the type of the returning value by adding the postfix string to the function declaration. In these cases, where the given function does not return any value, the type annotation void will give the compiler the information it requires to provide a proper type checking.

As we mentioned in the previous section, the TypeScript compiler is smart enough to infer types when no annotation is provided. In this case, the compiler will look into the arguments provided and the return statements to infer a returning type from it.

Functions in TypeScript can also be represented as expressions of anonymous functions, where we bind the function declaration to a variable:

var sayHello = function(name: string): string {
    return 'Hello, ' + name;
};

However, there is a downside of this syntax. Although typing function expressions this way is allowed, thanks to type inference, the compiler is missing the type definition in the declared variable. We might assume that the inferred type of a variable that points to a function typed as string is obviously a string. Well, it's not. A variable that points to an anonymous function ought to be annotated with a function type. Basically, the function type informs about both the types expected in the function payload and the type returned by the function execution, if any. This whole block, in the form of (arguments: type) => returned type, becomes the type annotation our compiler expects:

var sayHello: (name: string) => string = function(name: string): string{
  return 'Hello, ' + name;
};

Why such a cumbersome syntax, you might ask? Sometimes, we will declare variables that might depend on factories or function bindings. Then, it is always a good practice to provide as much information to the compiler as we can. This simple example might help you to understand better:

// Two functions with the same typing but different logic
function sayHello(input: string): string {
    return 'Hello ' + input;
}

function sayHi(input: string): string {
  return 'Hi ' + input;
}
        
// Here we declare the variable with its function type
var greetMe: (name: string) => string;

// Last, we assign a function to the variable
greetMe = sayHello;

This way, we also ensure that later function assignations conform to the type annotations set when declaring variables.

Function parameters in TypeScript

Due to the type checking performed by the compiler, function parameters require special attention in TypeScript.

Optional parameters

Parameters are a core part of the type checking applied by the TypeScript compiler. In other words, we cannot declare parameters and then do not included in the payload when executing the function afterwards. The same applies to even to those arguments in the function payload, which were not originally declared and annotated when defining the function. We obviously need a way to cope with this case scenario, so TypeScript offers this functionality by adding the ? symbol as a postfix to the parameter name we want to make optional:

function greetMe(name: string, greeting?: string): string {
    if (!greeting) {
        greeting = 'Hello';
    }
    return greeting + ', ' + name;
}

Note

When a parameter is marked as optional and not provided when executing the function, TypeScript will assign the null value to it. On the other hand, the rule of thumb is to put required parameters first and then optional parameters last.

Default parameters

TypeScript gives us another feature to cope with the scenario depicted earlier in the form of default parameters, where we can set a default value the parameter will assume when not explicitly populated upon executing the function. The syntax is pretty straightforward as we can see when we refactor the previous example here:

function greetMe(name: string, greeting: string = 'Hello'): string {
    return greeting + ', ' + name;
}

Just as with optional parameters, default parameters must be put right after the non-default parameters in the function signature.

Rest parameters

One of the big advantages of the flexibility of JavaScript when defining functions is the functionality to accept an unlimited non-declared array of parameters in the form of the arguments object. In a statically typed context such as TypeScript, this might be not possible, but it actually is by means of the Rest parameter's object. Here, we can define, at the end of the arguments list, an additional parameter prefixed by ellipsis and typed as an array:

function greetPeople(greeting: string, ...names: string[]): string {
    return greeting + ', ' + names.join(' and ') + '!';
}

alert(greetPeople('Hello', 'John', 'Ann', 'Fred'));

Note

It's important to note that the Rest parameters must be put at the end of the arguments list and can be left off whenever not required upon executing the function.

Overloading the function signature

Method and function overloading is a common pattern in other languages such as C#. However, implementing this functionality in TypeScript clashes with the fact that JavaScript, which TypeScript is meant to compile to, does not implement any elegant way to integrate this functionality out of the box. So, the only workaround possibly requires writing function declarations for each of the overloads and then writing a general-purpose function that will wrap the actual implementation and whose list of typed arguments and returning types are compatible with all the others:

function hello(name: string): string;
function hello(names: string[]): string;
function hello(names: any, greeting?: string): string {
  var namesArray: string[];
  
  if(Array.isArray(names)) {
    namesArray = names;
  } else {
    namesArray = [names];
  }
  
  if(!greeting) {
    greeting = 'Hello';
  }
  
  return greeting + ', ' + namesArray.join(' and ') + '!';
}

In the preceding example, we are exposing three different function signatures and each of them features different type annotations. We could even define different returning types if there was a case for that. For doing so, we should have just annotated the wrapping function with an any return type.

Better function syntax and scope handling with lambdas

ECMAScript 6 introduced the concept of fat arrow functions (also called lambda functions in other languages such as Python, C#, Java, or C++) as a way to both simplify the general function syntax and also to provide a bulletproof way to handle the scope of the functions that are traditionally handled by the infamous scope issues of tackling with the this keyword.

The first impression is its minimalistic syntax, where, most of the time, we will see arrow functions as single-line, anonymous expressions:

var double = x => x * 2;

The function computes the double of a given number, x, and returns the result, although we do not see any function or return statements in the expression. If the function signature contains more than one argument, we just need to wrap them all between braces:

var add = (x, y) => x + y;

This makes this syntax extremely convenient when developing functional operations such as map, reduce, and others:

var reducedArray = [23, 5, 62, 16].reduce((a, b) => a + b, 0);

Arrow functions can also contain statements. In that case, we will want to wrap the whole implementation in curly braces:

var addAndDouble = (x, y) => {
  var sum = x + y;
  return sum * 2;
}

Still, what does this have to do with scope handling? Basically, the value of this can point to a different context, depending on where we execute the function. This is a big deal for a language that prides itself on an excellent flexibility for functional programming, where patterns such as callbacks are paramount. When referring to this inside a callback, we lose track of the upper context and that usually forces us to use conventions such as assigning the value of this to a variable named self or that, which will be used later on within the callback. Statements containing interval or timeout functions make a perfect example of this:

function delayedGreeting(name): void {
    this.name = name;
    this.greet = function() {
      setTimeout(function() {
        alert('Hello ' + this.name);
      }, 0);
    }
}

var greeting = new delayedGreeting('Peter')
greeting.greet(); // alerts 'Hello undefined'

When executing the preceding script, we won't get the expected Hello Peter alert, but an incomplete string highlighting a pesky greeting to Mr. Undefined! Basically, this construction screws the lexical scoping of this when evaluating the function inside the timeout call. Porting this script to arrow functions will do the trick though:

function delayedGreeting(name): void {
    this.name = name;
    this.greet = function() {
      setTimeout(() => alert('Hello ' + this.name), 0);
    }
}

Even if we break down the statement contained in the arrow function into several lines of code wrapped by curly braces, the lexical scoping of this will keep pointing to the proper context outside the setTimeout call, allowing a more elegant and clean syntax.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset