[Javascript] Write Observable
function Observable(forEach) {
this._forEach = forEach;
}
Observable.prototype = {
forEach: function (onNext, onError, onCompleted) {
if (typeof onNext === "function") {
return this._forEach({
onNext,
onError: onError || function () {},
onCompleted: onCompleted || function () {},
});
} else {
return this._forEach(onNext);
}
},
map: function (transformer) {
const self = this;
return new Observable(function forEach(observer) {
return self.forEach(
(data) => {
observer.onNext(transformer(data));
},
(e) => {
observer.onError(e);
},
() => {
observer.onCompleted();
}
);
});
},
filter: function (preditionFn) {
const self = this;
return new Observable(function forEach(observer) {
return self.forEach(
(data) => {
if (preditionFn(data)) {
observer.onNext(data);
}
},
(e) => {
observer.onError(e);
},
() => {
observer.onCompleted();
}
);
});
},
take: function (maxCount) {
const self = this;
let count = 0;
return new Observable(function forEach(observer) {
return self.forEach(
(data) => {
const sub = observer.onNext(data);
count++;
if (count === maxCount) {
observer.onCompleted();
sub.dispose();
}
return sub;
},
(e) => {
observer.onError(e);
},
() => {
observer.onCompleted();
}
);
});
},
};
Observable.fromEvent = function (dom, eventName) {
return new Observable(function forEach(observer) {
const handler = (e) => observer.onNext(e);
dom.addEventListener(eventName, handler);
return {
dispose: () => {
dom.removeEventListener(eventName, handler);
},
};
});
};
Observable.fromObservations = function (obj) {
return new Observable(function forEach(observer) {
const handler = (e) => observer.onNext(e);
Object?.observe?.(obj, handler);
return {
dispose: () => {
Object?.unobserve?.(obj, handler);
},
};
});
};
Version 2:
class Observable {
constructor(subscribe) {
this._subscribe = subscribe;
}
subscribe(observer) {
return this._subscribe(observer);
}
static concnat(...observables) {
return new Observable((observer) => {
const copy = observables.slice();
let currentSubscription = null;
// using recurisve call to implement concat effect
// for each call, there we update currentSubscription
// when calling unsubscribe on currentSubscription
// complete() won't be triggered, therefore whole
// observables stops as well
const processObservable = () => {
if (!copy.length) {
observer.complete();
} else {
const observable = copy.shift();
currentSubscription = observable.subscribe({
next(data) {
observer.next(data);
},
error(err) {
observer.error(err);
currentSubscription.unsubscribe();
},
complete() {
processObservable();
}
});
}
};
processObservable();
return {
unsubscribe: () => {
currentSubscription.unsubscribe();
}
};
});
}
static timeout(time) {
return new Observable((observer) => {
const handle = setTimeout(() => {
observer.next();
observer.complete();
}, time);
return {
unsubscribe: () => {
clearTimeout(handle);
}
};
});
}
static fromEvent(dom, eventName) {
return new Observable((observer) => {
const handle = (e) => {
observer.next(e);
};
dom.addEventListener(eventName, handle);
return {
unsubscribe: () => {
dom.removeEventListener(eventName, handle);
}
};
});
}
// map is a operator, you need to subscribe it
map(transfomer) {
return new Observable((observer) => {
const subscription = this.subscribe({
next(data) {
let value;
try {
value = transfomer(data);
observer.next(value);
} catch (err) {
observer.error(err);
subscription.unsubscribe();
}
},
error(e) {
observer.error(e);
},
complete() {
observer.complete();
}
});
return subscription;
});
}
filter(predition) {
return new Observable((observer) => {
const subscription = this.subscribe({
next(data) {
try {
if (predition(data)) {
observer.next(data);
}
} catch (err) {
observer.error(err);
subscription.unsubscribe();
}
},
error(e) {
observer.error(e);
},
complete() {
observer.complete();
}
});
return subscription;
});
}
}
const button = document.getElementById("btn");
const buttonClick = Observable.fromEvent(button, "click");
const buttonClickOffset = buttonClick.map((pointerEvent) => {
return {
offsetX: pointerEvent.offsetX,
offsetY: pointerEvent.offsetY
};
});
const buttonClickOffsetRight = buttonClickOffset.filter((ev) => {
return ev.offsetX > 40;
});
buttonClickOffsetRight.subscribe({
next(e) {
console.log(e);
},
error(e) {
console.error(e);
},
complete() {
console.log("completed");
}
});