Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
staltz committed Mar 25, 2017
0 parents commit bf6036d
Show file tree
Hide file tree
Showing 30 changed files with 977 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
.vscode/
18 changes: 18 additions & 0 deletions GroupSubscription.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* A Subscription that can group other child Subscriptions together.
*/
function GroupSubscription() {
this.subscriptions = [];
}

GroupSubscription.prototype.add = function add(subscription) {
this.subscriptions.push(subscription);
};

GroupSubscription.prototype.unsubscribe = function unsubscribe() {
this.subscriptions.forEach(function (subscription) {
subscription.unsubscribe();
});
}

module.exports = GroupSubscription;
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License (MIT)

Copyright (c) 2017 André Staltz

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
153 changes: 153 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Toy RxJS

A tiny implementation of RxJS that actually works, for learning.

## Usage

`npm install toy-rx`

```js
const Rx = require('toy-rx')
Rx.Observable.fromEvent = require('toy-rx/fromEvent')
Rx.Observable.prototype.map = require('toy-rx/map')
Rx.Observable.prototype.filter = require('toy-rx/filter')
Rx.Observable.prototype.delay = require('toy-rx/delay')

Rx.Observable.fromEvent(document, 'click')
.delay(500)
.map(ev => ev.clientX)
.filter(x => x < 200)
.subscribe({
next: x => console.log(x),
error: e => console.error(e),
complete: () => {},
})
```

## Why

I made this so people can look into the implementation of a simple RxJS and feel like they can actually understand it. I mean, the implementation is literally this below:

```js
class Subscription {
constructor(unsubscribe) {
this.unsubscribe = unsubscribe;
}
}

class Subscriber extends Subscription {
constructor(observer) {
super(function unsubscribe() {});
this.observer = observer;
}

next(x) {
this.observer.next(x);
}

error(e) {
this.observer.error(e);
this.unsubscribe();
}

complete() {
this.observer.complete();
this.unsubscribe();
}
}

class Observable {
constructor(subscribe) {
this.subscribe = subscribe;
}

static create(subscribe) {
return new Observable(function internalSubscribe(observer) {
const subscriber = new Subscriber(observer);
const subscription = subscribe(subscriber);
subscriber.unsubscribe = subscription.unsubscribe.bind(subscription);
return subscription;
});
}
}

class Subject extends Observable {
constructor() {
super(function subscribe(observer) {
this.observers.push(observer);
return new Subscription(() => {
const index = this.observers.indexOf(observer);
if (index >= 0) this.observers.splice(index, 1);
});
});
this.observers = [];
}

next(x) {
this.observers.forEach((observer) => observer.next(x));
}

error(e) {
this.observers.forEach((observer) => observer.error(e));
}

complete() {
this.observers.forEach((observer) => observer.complete());
}
}
```

See? It fit easily in a README and you weren't scared of looking at it even though it's source code.

Where are all the operators? Well, here's `map` for instance:

```js
function map(transformFn) {
const inObservable = this;
const outObservable = Rx.Observable.create(function subscribe(outObserver) {
const inObserver = {
next: (x) => {
try {
var y = transformFn(x);
} catch (e) {
outObserver.error(e);
return;
}
outObserver.next(y);
},
error: (e) => {
outObserver.error(e);
},
complete: () => {
outObserver.complete();
}
};
return inObservable.subscribe(inObserver);
});
return outObservable;
}
```

What about `filter`, and `combineLatest` and all the rest? Just look for yourself, don't be afraid to click on the JS files in this project.

## How is this different to the official RxJS?

This is just meant for education. It's missing a ton of stuff that the [official RxJS](http://reactivex.io/rxjs/) covers, such as:

- Subscribe supports partial Observer objects
- Subscribe supports functions as arguments
- Dozens of useful operators
- The wonderful and underrated [Lift Architecture](https://github.com/ReactiveX/rxjs/blob/3ced24f2192289e441e88368c02c9df86bcb11e0/src/Observable.ts#L60-L72)
- Schedulers (and in turn, testing with marble diagrams)
- Corner cases covered against bugs
- Thorough documentation and examples
- Thorough tests
- TypeScript support

Overall, this project is a simplification of RxJS which describes "close enough" how RxJS works, but has a lot of holes and bugs because we deliberately are not taking care of many corner cases in this code.

Use this project to gain confidence peeking into the implementation of a library and get familiar with RxJS internals.

## Contributing

Don't bother submitting PRs for this project to add more operators and more bug safety. Let's just keep this simple enough to read for any developer with a few minutes of free time.
41 changes: 41 additions & 0 deletions combineLatest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const Rx = require('./index');
const GroupSubscription = require('./GroupSubscription');

/**
* Rx.Observable.combineLatest = require('toy-rx/combineLatest');
*/
module.exports = function combineLatest() {
const inObservables = Array.prototype.slice.call(arguments);
const transformFn = inObservables.pop();
const outObservable = Rx.Observable.create(function subscribe(outObserver) {
const values = inObservables.map((inObservable) => undefined);
const gotValue = inObservables.map((inObservable) => false);
const outSubscription = new GroupSubscription();
inObservables.forEach((inObservable, index) => {
const inObserver = {
next: (x) => {
values[index] = x;
gotValue[index] = true;
if (gotValue.every(x => x === true)) {
try {
var y = transformFn(...values);
} catch (e) {
outObserver.error(e);
return
}
outObserver.next(y);
}
},
error: (e) => {
outObserver.error(e);
},
complete: () => {
outObserver.complete();
}
}
outSubscription.add(inObservable.subscribe(inObserver));
});
return outSubscription;
});
return outObservable;
}
23 changes: 23 additions & 0 deletions delay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const Rx = require('./index');

/**
* Rx.Observable.prototype.delay = require('toy-rx/delay');
*/
module.exports = function delay(period) {
const inObservable = this;
const outObservable = Rx.Observable.create(function subscribe(outObserver) {
const inObserver = {
next: (x) => {
setTimeout(() => outObserver.next(x), period);
},
error: (e) => {
setTimeout(() => outObserver.error(e), period);
},
complete: () => {
setTimeout(() => outObserver.complete(), period);
}
};
return inObservable.subscribe(inObserver);
});
return outObservable;
}
10 changes: 10 additions & 0 deletions empty.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const Rx = require('./index');

/**
* Rx.Observable.empty = require('toy-rx/empty');
*/
module.exports = function empty() {
return Rx.Observable.create(function subscribe(observer) {
observer.complete();
});
}
31 changes: 31 additions & 0 deletions filter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const Rx = require('./index');

/**
* Rx.Observable.prototype.filter = require('toy-rx/filter');
*/
module.exports = function filter(conditionFn) {
const inObservable = this;
const outObservable = Rx.Observable.create(function subscribe(outObserver) {
const inObserver = {
next: (x) => {
try {
var passed = conditionFn(x);
} catch (e) {
outObserver.error(e);
return;
}
if (passed) {
outObserver.next(x);
}
},
error: (e) => {
outObserver.error(e);
},
complete: () => {
outObserver.complete();
}
};
return inObservable.subscribe(inObserver);
});
return outObservable;
}
12 changes: 12 additions & 0 deletions fromArray.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const Rx = require('./index');

/**
* Rx.Observable.fromArray = require('toy-rx/fromArray');
*/
module.exports = function fromArray(array) {
return Rx.Observable.create(function subscribe(observer) {
array.forEach(x => observer.next(x));
observer.complete();
return new Rx.Subscription(function unsubscribe() {});
});
}
13 changes: 13 additions & 0 deletions fromEvent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const Rx = require('./index');

/**
* Rx.Observable.fromEvent = require('toy-rx/fromEvent');
*/
module.exports = function fromEvent(eventTarget, eventType) {
return Rx.Observable.create(function subscribe(observer) {
eventTarget.addEventListener(eventType, observer.next);
return new Rx.Subscription(function unsubscribe() {
eventTarget.removeEventListener(eventType, observer.next);
});
});
}
Loading

0 comments on commit bf6036d

Please sign in to comment.