I was looking into Observables and their differences to EventEmitter and then stumbled upon Subjects ( which I can see Angulars EventEmitter is based off ).
It seems Observables are unicast vs Subjects that are multicast ( and then an EE is simply a subject that wraps .next in emit to give the correct interface ).
Observables seem easy enough to implement
class Observable {
constructor(subscribe) {
this._subscribe = subscribe;
}
subscribe(next, complete, error) {
const observer = new Observer(next, complete, error);
// return way to unsubscribe
return this._subscribe(observer);
}
}
Where Observer is just a wrapper that adds some try catches and monitors isComplete so it can clean up and stop observing.
For a Subject I came up with:
class Subject {
subscribers = new Set();
constructor() {
this.observable = new Observable(observer => {
this.observer = observer;
});
this.observable.subscribe((...args) => {
this.subscribers.forEach(sub => sub(...args))
});
}
subscribe(subscriber) {
this.subscribers.add(subscriber);
}
emit(...args) {
this.observer.next(...args);
}
}
which sort of merges into an EventEmitter with it wrapping .next with emit - but capturing the observe argument of the Observable seems wrong - and like I have just hacked up a solution. What would be the better way to produce a Subject (multicast) from an Observable (unicast)?
I tried looking at RXJS but I can't see how it's subscribers array ever gets populated :/
I think you can have a better understanding by using the debugger as well. Open a StackBlitz RxJS project, create the simplest example(depending on what you're trying to understand) and then place some breakpoints. AFAIK, with StackBlitz you can debug the TypeScript files, which seems great.
Firstly, the Subject class extends Observable:
export class Subject<T> extends Observable<T> implements SubscriptionLike { /* ... */ }
Now let's examine the Observable class.
It has the well-known pipe method:
pipe(...operations: OperatorFunction<any, any>[]): Observable<any> {
return operations.length ? pipeFromArray(operations)(this) : this;
}
where pipeFromArray is defined as follows:
export function pipeFromArray<T, R>(fns: Array<UnaryFunction<T, R>>): UnaryFunction<T, R> {
if (fns.length === 0) {
return identity as UnaryFunction<any, any>;
}
if (fns.length === 1) {
return fns[0];
}
return function piped(input: T): R {
return fns.reduce((prev: any, fn: UnaryFunction<T, R>) => fn(prev), input as any);
};
}
Before clarifying what's going on in the above snippet, it is important to know that operators are. An operator is a function which returns another function whose single argument is an Observable<T> and whose return type is an Observable<R>. Sometimes, T and R can be the same(e.g when using filter(), debounceTime()...).
For example, map is defined like this:
export function map<T, R>(project: (value: T, index: number) => R, thisArg?: any): OperatorFunction<T, R> {
return operate((source, subscriber) => {
// The index of the value from the source. Used with projection.
let index = 0;
// Subscribe to the source, all errors and completions are sent along
// to the consumer.
source.subscribe(
new OperatorSubscriber(subscriber, (value: T) => {
// Call the projection function with the appropriate this context,
// and send the resulting value to the consumer.
subscriber.next(project.call(thisArg, value, index++));
})
);
});
}
export function operate<T, R>(
init: (liftedSource: Observable<T>, subscriber: Subscriber<R>) => (() => void) | void
): OperatorFunction<T, R> {
return (source: Observable<T>) => {
if (hasLift(source)) {
return source.lift(function (this: Subscriber<R>, liftedSource: Observable<T>) {
try {
return init(liftedSource, this);
} catch (err) {
this.error(err);
}
});
}
throw new TypeError('Unable to lift unknown Observable type');
};
}
So, operate will return a function. Notice its argument: source: Observable<T>. The return type is derived from Subscriber<R>.
Observable.lift just creates a new Observable. It's like creating nodes within a liked list.
protected lift<R>(operator?: Operator<T, R>): Observable<R> {
const observable = new Observable<R>();
// it's important to keep track of the source !
observable.source = this;
observable.operator = operator;
return observable;
}
So, an operator(like map) will return a function. What invokes that function is the pipeFromArray function:
export function pipeFromArray<T, R>(fns: Array<UnaryFunction<T, R>>): UnaryFunction<T, R> {
if (fns.length === 0) {
return identity as UnaryFunction<any, any>;
}
if (fns.length === 1) {
return fns[0];
}
return function piped(input: T): R {
// here the functions returned by the operators are being called
return fns.reduce((prev: any, fn: UnaryFunction<T, R>) => fn(prev), input as any);
};
}
In the above snippet, fn is what the operate function returns:
return (source: Observable<T>) => {
if (hasLift(source)) { // has `lift` method
return source.lift(function (this: Subscriber<R>, liftedSource: Observable<T>) {
try {
return init(liftedSource, this);
} catch (err) {
this.error(err);
}
});
}
throw new TypeError('Unable to lift unknown Observable type');
};
Maybe it would be better to see an example as well. I'd recommend trying this yourself with a debugger.
const src$ = new Observable(subscriber => {subscriber.next(1), subscriber.complete()});
The subscriber => {} callback fn will be assigned to the Observable._subscribe property.
constructor(subscribe?: (this: Observable<T>, subscriber: Subscriber<T>) => TeardownLogic) {
if (subscribe) {
this._subscribe = subscribe;
}
}
Next, let's try adding an operator:
const src2$ = src$.pipe(map(num => num ** 2))
In this case, it will invoke this block from pipeFromArray:
// `pipeFromArray`
if (fns.length === 1) {
return fns[0];
}
// `Observable.pipe`
pipe(...operations: OperatorFunction<any, any>[]): Observable<any> {
return operations.length ? pipeFromArray(operations)(this) : this;
}
So, the Observable.pipe will invoke (source: Observable<T>) => { ... }, where source is the src$ Observable. By invoking that function(whose result is stored in src2$), it will also call the Observable.lift method.
return source.lift(function (this: Subscriber<R>, liftedSource: Observable<T>) {
try {
return init(liftedSource, this);
} catch (err) {
this.error(err);
}
});
/* ... */
protected lift<R>(operator?: Operator<T, R>): Observable<R> {
const observable = new Observable<R>();
observable.source = this;
observable.operator = operator;
return observable;
}
At this point, src$ is an Observable instance, which has the source set to src$ and the operator set to function (this: Subscriber<R>, liftedSource: Observable<T>) ....
From my perspective, it's all about linked lists. When creating the Observable chain(by adding operators), the list is created from top to bottom.
When the tail node has its subscribe method called, another list will be created, this time from bottom to top. I like to call the first one the Observable list and the second one the Subscribers list.
src2$.subscribe(console.log)
This is what happens when the subscribe method is called:
const subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);
const { operator, source } = this;
subscriber.add(
operator
? operator.call(subscriber, source)
: source || config.useDeprecatedSynchronousErrorHandling
? this._subscribe(subscriber)
: this._trySubscribe(subscriber)
);
return subscriber;
In this case src2$ has an operator, so it will call that. operator is defined as:
function (this: Subscriber<R>, liftedSource: Observable<T>) {
try {
return init(liftedSource, this);
} catch (err) {
this.error(err);
}
}
where init depends on the operator that is used. Once again, here is map's init
export function map<T, R>(project: (value: T, index: number) => R, thisArg?: any): OperatorFunction<T, R> {
return operate( /* THIS IS `init()` */(source, subscriber) => {
// The index of the value from the source. Used with projection.
let index = 0;
// Subscribe to the source, all errors and completions are sent along
// to the consumer.
source.subscribe(
new OperatorSubscriber(subscriber, (value: T) => {
// Call the projection function with the appropriate this context,
// and send the resulting value to the consumer.
subscriber.next(project.call(thisArg, value, index++));
})
);
});
}
source is in fact src$. When source.subscribe() is called, it will end up calling the callback provided to new Observable(subscriber => { ... }). Calling subscriber.next(1) will call the (value: T) => { ... } from above, which will call subscriber.next(project.call(thisArg, value, index++));(project - the callback provided to map). Lastly, subscriber.next refers to console.log.
Coming back to Subject, this is what happens when the _subscribe method is called:
protected _subscribe(subscriber: Subscriber<T>): Subscription {
this._throwIfClosed(); // if unsubscribed
this._checkFinalizedStatuses(subscriber); // `error` or `complete` notifications
return this._innerSubscribe(subscriber);
}
protected _innerSubscribe(subscriber: Subscriber<any>) {
const { hasError, isStopped, observers } = this;
return hasError || isStopped
? EMPTY_SUBSCRIPTION
: (observers.push(subscriber), new Subscription(() => arrRemove(this.observers, subscriber)));
}
So, this is how Subject's list of subscribers are is populated. By returning new Subscription(() => arrRemove(this.observers, subscriber)), it ensures that then subscriber unsubscribes(due to complete/error notifications or simply subscriber.unsubscribe()), the inactive subscriber will be removed from the Subject's list.
Related
I am trying to write a function that accepts an API interface (I've created a sample here), and wraps a Proxy around it, so that any calls to the that API's methods get intercepted, and I can do some logging, custom error handling etc. I am having a terrible time with the types. This is similar to another question I have asked (Writing wrapper to third party class methods in TS), but uses a completely different approach than that one, based on some feedback I got.
Currently I am getting
Element implicitly has an 'any' type because expression of type 'string | symbol' can't be used to index type 'API'. No index signature with a parameter of type 'string' was found on type 'API'. which makes sense given that sayHello is not strictly a string as far as typescript is concerned, but I do not know the best way to be able to get methods on this class without uses the property accessor notation.
class API {
sayHello(name: string) {
console.log(“hello “ + name)
return name
}
}
export default <T extends API>(
api: T,
) =>
new Proxy(api, {
get(target, prop) {
if (typeof target[prop] !== "function") { // type error here with "prop"
return target[prop]; // and here
}
return async (...args: Parameters<typeof target[prop]>) => {
try {
const res = await target[prop](...args); // and here
// do stuff
return res
} catch (e) {
// do other stuff
}
};
},
});
Is this possible in TS?
TypeScript doesn't currently model the situation when a Proxy differs in type from its target object. There's a longstanding open feature request for this at microsoft/TypeScript#20846, and I don't know if or when the situation will change.
For now if you want to do this you'll need to manually describe the expected return type of your proxy function, and use type assertions liberally inside the implementation in order to suppress any errors. This means you'll need to verify that your function is type safe yourself; the compiler won't be able to help much.
Here's one possible approach:
const prox = <T extends API>(api: T) => new Proxy(api, {
get(target: any, prop: string) {
if (typeof target[prop] !== "function") {
return target[prop];
}
return async (...args: any) => {
try {
const res = await target[prop](...args);
return res;
} catch (e) { }
};
},
}) as any as { [K in keyof T]: AsyncifyFunction<T[K]> };
type AsyncifyFunction<T> = T extends (...args: infer A) => infer R ?
(...args: A) => Promise<Awaited<R>> : T;
The idea is that prox(api) returns a version of api where every non-function property is the same, but every function property has been changed to an async version that returns a Promise. So if api is of type T, then prox(api) is of mapped type { [K in keyof T]: AsyncifyFunction<T[K]> }, where AsyncifyFunction<T> is a conditional utility type that represents the transformation of each property type. If X is a function type with argument tuple A and return type R, then AsyncifyFunction<X> is (...args: A) => Promise<Awaited<R>>, using the Awaited<T> utility type to deal with any nested promises (we don't want a Promise<Promise<number>> to come out of this, for example).
Okay, let's test it:
class Foo extends API {
str = "abc";
double(x: number) { return x * 2 };
promise() { return Promise.resolve(10) };
}
const y = prox(new Foo());
/* const y: {
str: string;
double: (x: number) => Promise<number>;
promise: () => Promise<number>;
sayHello: (name: string) => Promise<void>;
} */
So, according to the compiler, y has a string-valued str property, and the rest of its properties are asynchronous methods that return promises. Notice that the promise() method returns Promise<number> and not Promise<Promise<number>>.
Let's make sure it works as expected:
console.log(y.str.toUpperCase()) // "ABC"
y.sayHello("abc") // "helloabc"
y.double(123).then(s => console.log(s.toFixed(2))) // "246.00"
y.promise().then(s => console.log(s.toFixed(2))) // "10.00"
Looks good!
Playground link to code
You can try add index signature to your class:
class API {
/* -- index signature -- */
[index:string|symbol]: any;
sayHello(name: string) {
console.log("hello" + name)
}
}
export default <T extends API>(
api: T,
) =>
new Proxy(api, {
get(target, prop) {
if (typeof target[prop] !== "function") {
return target[prop];
}
/* -- had to store it in a variable -- */
const data = target[prop];
return async (...args: Parameters<typeof data>) => {
try {
const res = await target[prop](...args);
// do stuff
return res.data;
} catch (e) {
// do other stuff
}
};
},
});
I have the following function that traverse the tree-like object and it working fine so far.
const traverse = async (menuInputs: MenuInput[], parent: Menu = null) => {
for (const input of menuInputs) {
const entity = toEntity(input, parent);
const parentMenu = await this.menuService.create(entity).toPromise();
if (isEmpty(input.children)) {
continue;
}
await traverse(input.children, parentMenu);
}
};
My question is how can i invoke method this.menuService.create(entity) that is actually return an Observable<Menu> without convert it to Promise, are there any RxJS way of doing this ?
I've got something like this for recursive observable calls using the HttpService. You can probably work with something similar.
#Injectable()
export class CharacterReaderApiService implements CharacterReader {
private characters: SwapiCharacter[] = [];
constructor(private readonly http: HttpService) {}
async getCharacters(): Promise<Character[]> {
console.log('Querying the API for character data...');
return this.callUrl('https://swapi.dev/api/people')
.pipe(
map((data) => {
return data.map(this.mapPerson);
}),
tap(async (data) =>
promises.writeFile(join(process.cwd(), characterFile), JSON.stringify(data)),
),
)
.toPromise();
}
private callUrl(url: string): Observable<SwapiCharacter[]> {
return this.http.get<SwapiResponse>(url).pipe(
map((resp) => resp.data),
mergeMap((data) => {
this.characters.push(...data.results);
return iif(() => data.next !== null, this.callUrl(data.next), of(this.characters));
}),
);
}
private mapPerson(data: SwapiCharacter): Character {
return {
name: data.name,
hairColor: data.hair_color,
eyeColor: data.eye_color,
gender: data.gender,
height: data.height,
};
}
}
The important thing is keeping a running array of the values so that they can be referenced later on.
By using mergeMap with iif we're able to recursively call observables and work with the result as necessary. If you don't like the idea of a class variable for it, you could make the running array a part of the callUrl (or similar) method's parameters and pass it on as needed.
I'd strive to change toEntity() and menuService.create() to receive the list of children rather than the parent so I could use forkJoin:
const buildMenu = (input: MenuInput) =>
forkJoin(
input.children.map(childInput => buildMenu(childInput, menu))
).pipe(
flatMap(childMenus => this.menuService.create(toEntity(input, childMenus)))
);
In my web app's client code I have a class responsible for a bunch of websocket IO. This class has a global itemUpdatedObservable that various parts of the UI can subscribe to to do little things. There is also a public function UpdateItem which returns a promise-esq Observable. When the item is updated in response to the call to UpdateItem I want both the returned observable and global observable to emit. The returned observable should also complete after emitting.
I have come up with this solution:
// Singleton
class API {
readonly itemUpdatedObservable: Observable<Item>;
private pendingItemUpdates: { [id: string]: Observer<Item> };
constructor() {
this.itemUpdatedObservable = new Observable(observer => {
socketio.on('itemUpdated', res => {
// do a bunch of validation on item
// ...
if (!res.error) {
observer.next(res.item);
} else {
observer.error(res.error);
}
let pendingObs = pendingItemUpdates[res.id]
if (pendingObs) {
if (!res.error) {
pendingObs.next(res.item);
} else {
pendingObs.error(res.error);
}
pendingObs.complete()
delete pendingItemUpdates[res.id];
}
})
});
this.pendingItemUpdates
}
public UpdateItem(item: Item): Observable<Item> {
const o = new Observable(observer => {
let id = uniqueId(); // Some helper somewhere.
this.pendingItemUpdates[id] = observer;
socketio.emit('updateitem', {item: item, id: id});
}).publish();
o.connect();
return o;
}
}
My question is if there is a cleaner, shorter way of doing this? I have something like 10+ observables in addition to itemUpdatedObservable that all are events for different Object types. This code is messy and unwieldy especially when I am writing it 10x over. Is there a way to streamline the two observables such that I am only calling observable.next(...) or observable.error(...) once?
The above code blob is a simplification of my actual code, there is a lot more validation and context-specific values and parameters in reality.
Maybe you can start with creating some reusable socket function which return observable.
const socketOn = (event) => {
return Observable.create(obs => {
socketio.on(event, res => {
if (!res.error) {
obs.next(res.item);
} else {
obs.error(res.error);
}
})
}).share()
}
// usuage
itemUpdated$=socketOn('itemUpdated')
itemUpdated$.map(res=>...).catch(e=>...)
I am developing angular application where I need apply following mechanism:
My view has 2 parts (list of items and detail if selected item). User can click on some item, and next service fetch additional data for that item and show them in detail view. Also I want select first item automatically on start if is available.
Here is my service:
#Injectable()
export class ItemService {
private url: string;
private itemSource = new BehaviorSubject<Item>(null);
selectedItem = this.itemSource.asObservable();
constructor(private http: HttpClient) {
this.url = 'http://localhost:8080/api/item';
}
getItems(): Observable<Item[]> {
let observable = this.http.get<Item[]>(this.url)
.map(items => items.map(item => {
return new Item(item);
}));
return observable;
}
selectItem(item: Item) {
return this.http.get<Item>(this.url + '/' + item.id)
.map(item => new Item(item))
.subscribe(t => this.itemSource.next(t));
}
}
in detail component I am subscribing selected item like this:
ngOnInit() {
this.itemService.selectedItem.subscribe(item => this.selectedItem = item);
}
and following code is from my component where I displayed list of items. I also want set selected item after data are subscribed but my code isn't works. I am iterating items[] property in html template and data are showed, but when I access to this array after I subscribed data I got undefined. Can you please fix my code? Thanks!
public items = [];
constructor(private itemService: ItemService) { }
ngOnInit() {
this.itemService.getItems()
.subscribe(
data => this.items = data,
err => console.log(err),
function () {
console.log('selected data', this.items); // this prints undefined
if (this.items && this.items.length) {
this.itemService.selectedItem(this.items[0])
}
});
}
Your problem is that you are not using an arrow function for the complete callback in your call to subscribe. As you see, you are using arrow functions for next and error.
When you define a new function with function(...) {...} you're creating a new context, and so the this keyword changes its meaning. The difference between arrow function and normal functions (besides being more elegant, in my opinion), is that arrow functions do not define a new context for this, and so the meaning of that keyword is the same as in the context they are defined. So, in your next and error callbacks, this is your component, but in your call to complete, this is, most surely, a reference to window, which does not have an items property, hence the undefined.
Change your code to:
public items = [];
constructor(private itemService: ItemService) { }
ngOnInit() {
this.itemService.getItems()
.subscribe(
data => this.items = data,
err => console.log(err),
() => {
console.log('selected data', this.items); // this prints undefined
if (this.items && this.items.length) {
this.itemService.selectedItem(this.items[0])
}
});
}
I imagine you used the function keyword there because that function had not arguments, but you can express that with the syntax () => expression, or () => {...}
data => this.items = data, after all, is a simpler and more elegant way of writing
(data) => { return this.items = data; }
I'd like to create a decorator that can be applied to methods,
It's goal is to control whether you're allowed to run a certain method or not.
Meaning it should have a certain condition, if it passes it'll run as usual (in the same context as well)
Here's a shot I took on this but failed due to private members the object has, and now had no access to when I ran the function:
return function(target:any, propertyKey: string, descriptor: PropertyDescriptor){
var funcToRun = descriptor.value;
descriptor.value = () => {
if(true) { //if has permissions
return p.call(target);
}
}
}
Thanks in advance.
I wouldn't change the passed descriptor but instead would return a changed copy.
Here's a working version of what you asked for:
function deco(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const newDescriptor = Object.assign({}, descriptor);
newDescriptor.value = function () {
if (this.x > 0) {
return descriptor.value.apply(this, arguments);
} else {
throw new Error(`can't invoke ${ propertyKey }`);
}
}
return newDescriptor;
}
class A {
constructor(private x: number) {}
#deco
methodA() {
console.log("A.methodA");
}
}
let a1 = new A(10);
a1.methodA(); // prints: "A.methodA"
let a2 = new A(-10);
a1.methodA(); // throws error
(code in playground)