Yes, the title is not a typo. We are actually going to reverse engineer RxJs (tons of code to come ;) ). But before we proceed let me tell you why I embarked on this crazy endeavor.
As programmers, we are curious by nature. I work with reactive libraries such as RxJs and React.js everyday. However, one bright morning I got curious about how these frameworks leveraging reactive programming under the hood.
Do I really understand what reactive programing is? How does it actually work? I asked myself.
After a weekend of digging through blog posts and reading books I sort of got the concept. However, I figured reverse engineering something would be a great way to actually nail down the concepts, so I decided to reverse engineer RxJs.
A Quick Intro:
Reactive programming is programming with asynchronous data streams.
The Data streams could be anything, it could be user inputs (i.e. position of your mouse, click events), it could be streams of data from a server (i.e. your twitter feed, or real time data from a socket server). Our Application will react according to these streams of data.
For example as you are receiving twitter feeds in realtime in your application state will change. Maybe you want to put the most popular tweets on top. So your application is subscribed to the incoming streams of data and it reacts to the data and puts the most popular tweet on top. In brief this concept of subscribing to data streams and changing the application accordingly is reactive programing.
Are you bored? Trust me this is not going to be one of those blog posts with lots of concepts. We will be diving into code now.
Let’s build a class called Observable
since it is the most fundamental building block of RxJs.
class Observable {
constructor() {
this.fnArray = [];
}
subscribe() {}
emit() {}
}
const o = new Observable();
Alright we just created a basic class called Observable with two methods. We initialized an empty list called fnArray. This array will hold all our subscribed objects.
Let’s implement the subscribe
method first. This method will take in a function as an argument and push it in our fnArray.
subscribe(fn) {
this.fnArray.push(fn);
}
Now let’s implement the emit
function as well. The job of the emit function is to loop over the fnArray
and execute those functions one after another.
emit(v) {
for (let fun of this.fnArray) {
fun(v);
}
}
We can also replace that for loop with a map. But why tho? Well that's what the cool kids in JS land are doing apparentnow.and curry functions are kindda cool!! so let’s do that now.
emit(v) {
- for (let fun of this.fnArray) {
- fun(v);
- }
+ this.fnArray.map(fun => fun(v))
}
Okay, now let’s use our newly created class.
function printFunction(thing) {
console.log(`I will print the ${thing}`)
}
const o = new Observable();
o.subscribe(printFunction);
First we created a function printFunction
that prints whatever variable passed in. We initialized a new observable instance and called the subscribe
method on it and passed in our printFunction
as argument.
Remember the printFunction
will be stored in the fnArray
. Now what do you think will happen if we call the emit method? Let’s try
o.emit("Apple");
o.emit("Orange");
o.emit("Pear");
This gives us the following output
I will print the Apple
I will print the Orange
I will print the Pear
Okay now we are able to subscribe to a function or event and emit something based on that function. Here’s how the entire code looks like so far.
class Observable {
constructor() {
this.fnArray = [];
}
subscribe(fn) {
this.fnArray.push(fn);
}
emit(v) {
this.fnArray.map(fun => fun(v));
}
}
function printFunction(thing) {
console.log(`I will print the ${thing}`);
}
const o = new Observable();
o.subscribe(printFunction);
o.emit("Apple");
o.emit("Orange");
o.emit("Pear");
Now let’s get into the interesting bits. We can subscribe to multiple functions. For example we can do something like this
o.subscribe(x => console.log(x * 2));
o.subscribe(x => console.log(x + 2));
o.emit(4)
which returns
// 8
// 6
because our emit call looped over all the function in that array of function that is initialized on the class constructor.
notice that I am using arrow function now. We are also able to compose our functions with any combinations we wish.
const square = num => num * num;
o.subscribe(x => printFunction(x * 2));
o.subscribe(x => printFunction(square(x)));
o.emit(4);
// outputs
// I will print the 8
// I will print the 16
In the first scenario we composed our function with printFunction
. In the second scenario we created a square
function and composed it with printFunction
.
This is sort of cool isn’t it?
Alright we can compose functions but we need a better way to compose them. Something more comprehensive like pipe
in RxJS. So let’s build that mechanism.
const pipe = (f, g) => x => g(f(x));
We defined a new function called pipe that takes 2 functions as arguments and returns a function that takes a parameter then returns the composed function of f of g.
What did just happen
We took 2 functions as argument. Then we took another value as argument and we apply first function f
with value x
. Then we took the return value of f(x)
and applied function g
.
This could be a little confusing, if you are I highly recommend you do some reading on currying function
in JavaScript.
Using the pipe function now we can do something like this
o.subscribe(
pipe(
square,
printFunction,
)
)
o.emit(4);
// outputs
// I will print the 16
But we have a problem here. We want to be able to pass in any numbers of functions and then we should be able to compose them. So if we have f,g,h,k ⇒ k(h(g(f))).
So we'll modify our pipe like this
const pipe = (...funcs) => x => funcs.reduce((effects, f) => f(effects), x);
what functional magic is this? Well, first of all we are taking in a number of functions with our spread operator. (...funcs)
part specifies that we can take in any number of functions in order. Then we are taking in a value x
as an argument to operate on. funcs.reduce
will go over each functions and return the updated value of x
and pass it in the next function that’s in the series. Think of this as a series execution. At the end of our execution x
is still the same because we do not mutate values in pure functions.
More on pure function in my next article so stay tuned 😊
Now let me show you why we did this. Let’s take a look at the code below
o.subscribe(
pipe(
square,
double,
square,
printFunction
)
);
o.emit(2);
// outputs
// I will print the 64
You see now can compose functions without really caring much for their orders, and we can also keep the data immutable.
However, our implementation is missing one thing. We can not collect our data in between pipe. What I mean by this is, we can not break and collect our value after the second double
is applied. RxJs has a tap
method that allows this. So let’s go and implement a tap
method.
const tap = fun => x => {
fun(x);
return x;
};
For this method we take in a function and a value and we apply function with the value and
return the original value. This way now we can tap and take out values in a specific position of the pipe stream.
o.subscribe(
pipe(
square,
double,
tap(printFunction),
square,
printFunction
)
);
o.emit(2);
// outputs
// I will print the 8
// I will print the 64
This is pretty much it. We technically have the barebone functionality of a reactive library like RxJS. *** Now I want to show you a practical implementation of our reactive library***.
So let’s say we have some incoming asynchronous data. (i.e. mouse pointer position could be an example) and based on that data I want to perform some state change in my application. So this is how we will handle this with our reactive library
o.subscribe(pipe(
filter(x => {
if(x > 0) {
console.log('In Range')
return x;
}
console.log('Out of Range')
return 0
}),
square,
tap(printFunction),
));
o.emit(2);
o.emit(-4);
o.emit(8);
o.emit(4);
// outputs
// In Range
// I will print the 4
// Out of Range
// I will print the 0
// In Range
// I will print the 64
// In Range
// I will print the 16
So we can do this funnel like data filtering
with our library just like RxJS. I hope this gave you some insight on how RxJS operates behind the scene.
In Part 2 we will further break down all the operations of RxJS. Stay tuned, leave a comment if you have any feedback, follow me for more articles like this
⚡️⚡️⚡️⚡️⚡️⚡️
Enjoying the ride so far? head over to part 2 🕟 🕔 🕠 🕕.
Top comments (6)
I just started learning rxjs a few months ago and found this to be super informative. Thanks!
Thank you. Means a lot. I am glad you liked it
Excellent article! I don’t see anything wrong with using for...of in your first implementation. Also, if we want to be technical about it, the refactor should’ve used forEach, not map.
Yeah, plus
map
returns a new array, which is not really needed in this case, so something likeforEach
fits better.Good one ;)
Really nice job! Good work.