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
Operationtype. AnOperationis a function that accepts twonumbervalue and returns a number. - Then, we define two
Operationfunctions:add()andsubtract(). - The
calculatoris a higher-order function that has astartvalue and returns a function, which accepts anOperationfunction and theoperandnumber value. The "inner" function invokes theoperation()function using thestartandoperandvalues. - 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
Observableinstance. Let's refer to this Observable as the "outer" Observable. - Then, we declare
ito start with0. - The
addconstant is a new Observable that emits a next notification that multiplies the incrementedivalue by10. Let's refer to this Observable as the "inner" Observable. - We use
window.setInterval()to emit to the Observer the inneraddObservable 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
characterCountfunction accepts an optionaltimeoutargument and returns a new function. - The returned function accepts the
sourceObservable. - We are using the
windowTimeoperator to slice up thesourceObservable based on thetimeoutand 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.