Hidden JavaScript functional patterns in TypeScript
TypeScript has enabled JavaScript codebases to scale like no other technology before. It has mostly to do with a flexible type system the TypeScript provides. Type checking helps to avoid runtime surprises, but it arguably also makes JavaScript projects look more like written in Java, C# or other object-oriented programming languages. There is nothing wrong with object-oriented approach or object-oriented fans writing TypeScript how they know best. The point is that JavaScript has a functional side and a lot can be gained by using it. Moreover, since TypeScript is so flexible it can also help to make functional code type safe.
Let's explore some of the functional patterns which are familiar to JavaScript programmers, but may be less known to programmers who mostly work with TypeScript.
Functional ForEach
Functional forEach is perfect when an array needs to be iterated and there is no need to break the iteration. forEach is especially useful when combined with function passing.
let x = [7, 8, 4, 3];
x.forEach((a,i)=>{
console.log(`Index: ${i}, Value: ${a}`);
});
// Console output:
// Index: 0, Value: 7
// Index: 1, Value: 8
// Index: 2, Value: 4
// Index: 3, Value: 3
Map
Map produces a new array with the same size where every entry is a result of the Map function execution.
let x = [7, 8, 4, 3];
let result = x.map((a)=>{
return `N: ${a}`;
});
console.log(result);
// Console output:
// ["N: 7", "N: 8", "N: 4", "N: 3"]
Filter
Filter produces a new array where items can be omitted based on the result of the passed in function.
let x = [7, 8, 4, 3];
let result = x.filter((a)=>{
return a < 8;
});
console.log(result);
// Console output:
// [4, 3]
Reduce
Reduce is one of the more difficult to grasp functional patterns. Reduce, as the name suggests, reduces an array to a single value. Summing up all numbers is a classical example, but this pattern can be used more creatively for example for chaining promises to execute after each other completes.
let x = [7, 8, 4, 3];
let result = x.reduce((a, b)=>{
return a+b;
}, 0);
console.log(result);
// Console output:
// 22
// Promise chaining using Reduce
let promises = [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)];
let result = promises.reduce(function(a , b){
return b.then(a);
}, Promise.resolve());
result.then(console.log);
// Console output:
// 3
Combining Array manipulation functions together
The best part of single purpose functional patterns is the creatives ways they can be used together.
let x = [7, 8, 4, 3];
let result = x.map(a=>a+1).filter(a=>a<8).reduce((a,b)=>a+b, 0);
console.log(result);
// Console output:
// 9
Function passing
Function passing allows to create a reference to a function and pass it around to other functions, which decides when to invoke the function.
let sum = function(a, b){ return a+b; };
let calculator = function(a, operation, b){ return operation(a, b); };
let result = calculator(3, sum, 5);
console.log(result);
// Console output:
// 8
Function closures
Function closures are mostly used without thinking about, but with some awareness can be quite powerful and memory-leak free. Closures allow function to keep a reference to a variable for duration of function's lifetime.
let secretAdder = function(){
const secret = 42;
return function(a){
// reference to secret variable will be kept until secretAdder is garbage-collected.
return a + secret;
}
}();
let result = secretAdder(10);
console.log(result);
// Console output:
// 52
Function currying
Function currying is a way of converting function with multiple arity to function with less arity.
let readFile = function(fileName, errorLogger){
errorLogger.log("reading...")
return "Content of " + fileName;
};
let readFileWithConsoleLog = function(fileName){
return readFile(fileName, console);
};
let result = readFileWithConsoleLog("test.txt");
console.log(result);
// Console output:
// reading...
// Content of text.txt