Higher-order Function Operator
Higher-order Functions
Let's quickly recap what a higher-order function is, and take note of the higher-order Observable operators in RxJS.
To quote wikipedia:
A higher-order function is a function that does at least one of the following:
- takes one or more functions as arguments (i.e. procedural parameters),
- returns a function as its result.
Let's take a look at an example of a higher-order function in JavaScript. In this example we'll use a higher-order function in order to calculate values.
type Operation = (a: number, b: number) => number;
const add: Operation = (a: number, b: number) => a + b;
const subtract: Operation = (a: number, b: number) => a - b;
const calculator = (start = 0) => (operation: Operation, operand: number) =>
operation(start, operand);
let sum = calculator(5)(add, 4);
console.log(sum);
sum = calculator(5)(subtract, 4);
console.log(sum);
Let's go through the code example above that uses a higher-order function:
- First, we declare a new
Operation
type. AnOperation
is a function that accepts twonumber
value and returns a number. - Then, we define two
Operation
functions:add()
andsubtract()
. - The
calculator
is a higher-order function that has astart
value and returns a function, which accepts anOperation
function and theoperand
number value. The "inner" function invokes theoperation()
function using thestart
andoperand
values. - Now, let's look at two implementation that use the
calculator()
higher-order function. First, we use theadd()
function, and then we use thesubtract()
function.
Higher-order Observables
Similar to a higher-order function, a higher-order Observable is an Observable that emits one or more Observables.
Let's look at a simple implementation of a higher-order Observable:
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
const higherOrderObservable = new Observable((observer) => {
let i = 0;
const add = new Observable((_observer) => _observer.next(i++ * 10));
const handle = window.setInterval(() => observer.next(add), 1000);
return () => window.clearInterval(handle);
});
higherOrderObservable
.pipe(
switchMap((observable: Observable<number>) =>
observable.pipe(tap(console.log))
)
)
.subscribe();
In this example we are creating a higher-order Observable that emits an Observable:
- First we create a new
Observable
instance. Let's refer to this Observable as the "outer" Observable. - Then, we declare
i
to start with0
. - The
add
constant is a new Observable that emits a next notification that multiplies the incrementedi
value by10
. Let's refer to this Observable as the "inner" Observable. - We use
window.setInterval()
to emit to the Observer the inneradd
Observable every 1000 milliseconds. - When the outer Observable is unsubscribed from the teardown function is invoked, which clears the interval.
- Finally, we subscribe to the outer Observable.
The
switchMap()
operator receives an Observable as the next notification. TheswitchMap()
subscribes to the inner Observable and maps to the next notification emitted by the inner Observable.
Higher-order Function as Operator
Having reviewed both higher-order functions and Observables, let's bring these two concepts together to create a new operator using a higher-order function.
The goal of the operator is to calculate the characters entered into a textarea per second.
import { fromEvent, Observable, from } from 'rxjs';
import { count, filter, map, mergeMap, windowTime } from 'rxjs/operators';
function characterCount(timeout = 1000) {
return (source: Observable<string>) =>
source.pipe(
windowTime(timeout),
mergeMap((value) =>
from(value).pipe(
count(),
filter((count) => count > 0)
)
)
);
}
const input = document.getElementById('input') as HTMLTextAreaElement;
const output = document.getElementById('output') as HTMLInputElement;
fromEvent(input, 'keyup')
.pipe(
map(() => input.value),
characterCount()
)
.subscribe({
error: console.error,
next: (count) => (output.value = count.toString()),
complete: () => console.log('complete')
});
Let's review our characterCount
operator.
- First, we can see right away that we have created a higher-order function.
The
characterCount
function accepts an optionaltimeout
argument and returns a new function. - The returned function accepts the
source
Observable. - We are using the
windowTime
operator to slice up thesource
Observable based on thetimeout
and emit a new Observable. - The
mergeMap()
operator's source Observable is the string of characters entered within the window of thetimeout
. We use thefrom()
operator to create a new Observable that emits for each character, and thencount()
the number of next notification, filtering out those counts that are not greater than0
.