I am a fan of functional programming. The domain can be very complex but some the parts are simple. Even so that a lot of developers probably don't even know they are using functional programming. Some developers do not like to use functional programming because of how much memory it takes to respect immutability specially in the case of arrays but this library can help use functional programming in Typescript in an efficient way.
This library is FxTS.
Unlocking the power of lazy evaluation
FxTS introduces a novel approach to array manipulation compared to the default ones when using map
, filter
and such by introducing the concept of lazy evaluation. Unlike traditional methods that demand iterating through entire arrays regardless of necessity, FxTS uses generators to go over array elements. This means the iteration only occurs when values are explicitly needed, optimizing resource consumption and significantly boosting performance.
Let's compare how it behaves compared to the traditional Array functions like map
, slice
, sort
, etc.
Say you want the top 5 elements of an array. When we use the Array functions we go over all the elements many times.
[1, 2, 3, 4, 5]
.filter((a) => a % 2 === 0) // [2, 4, 6, 8]
.slice(0, 2); // [2, 4]
.map((a) => a + 10) // [12, 14]
Using FxTS we process only the until the two elements that satisfies the filter. This can make the difference when the array is large.
pipe(
[1, 2, 3, 4, 5],
map(a => a + 10), // [12, 14]
filter(a => a % 2 === 0), // [12, 14]
take(2), // [12, 14]
toArray
);
Here's an example of how it evaluates.
The awesomness of generators
At the core of FxTS lies the ingenious use of generators, a feature intrinsic to JavaScript. By embracing generators, FxTS provides us with a gateway to seamlessly handle infinite values within arrays. It's like having a superpower when dealing with vast datasets or scenarios where continuous, endless streams of data must be efficiently processed.
Here's an example that is too simple but helps to see how it works.
pipe(
range(Infinity),
filter((a) => a % 2 === 0), // [0, 2]
map((a) => a * a), // [0, 4]
take(2), // [0, 4]
reduce(sum), // 4
);
Handling promises
FxTS allows to easily handle promises sequentially and concurrently. The benefit is really in the latter.
const fetchMovie = async (year: number) =>
fetch(`https://api.movie.com/${year}`);
const moviesByYear = async (year: number, howManyYears: number) =>
pipe(
range(year, Infinity),
toAsync,
map(fetchMovie),
map((res) => res.json()),
take(howManyYears),
toArray
);
await moviesByYear(2020, 3);
Here's an example where a RapidAPI is actually called to get the movies for a year. Error handling is the same as ever using try catch.
type Movie = { imdb_id: string; title: string };
type APIResponse = {
links: { next: string; previous: string };
count: number;
results: Movie[];
};
const fetchMovie = async (year: number) => {
console.log(`getting movies for ${year}`);
return fetch(
`https://moviesminidatabase.p.rapidapi.com/movie/byYear/${year}/`,
{
headers: {},
},
);
};
const getMoviesForYear = async (year: number) =>
pipe(
year,
fetchMovie,
(res) => res.json(),
(response: APIResponse) => response.results,
);
try {
return getMoviesForYear(2020);
} catch (err) {
console.log(err);
}
FxTS allow to easily work with concurrent promises. Here's a sample of the code I've used in my Tiled Hacker news project. In this case promises are executed by batch of 10. In case of an error when calling getStoryById
it's not blocking the execution of promises in the batch or the execution of other batches. It's up to you to determine if it's what you need.
const { pageOfStoryIds } = await getCachedPaginatedStoryIds(
"top",
pageIndex,
);
const stories = pipe(
pageOfStoryIds,
toAsync,
map(getStoryById),
concurrent(10),
toArray,
);
In conclusion FxTS allows to easily work with array of sync and async data. It is a solid tool to work with arrays using the functional programming style.
Top comments (0)