Is there a good way to check if not completed Observable is empty at that exact time?
let cache = new ReplaySubject<number>(1);
...
// Here I want to know if 'cache' still empty or not. And, for example, fill it with initial value.
cache.isEmpty().subscribe(isEmpty => {
if (isEmpty) {
console.log("I want to be here!!!");
cache.next(0);
}
});
// but that code does not work until cache.complete()
Actually, it's not that simple and the accepted answer is not very universal. You want to check whether ReplaySubject is empty at this particular point in time.
However, if you want to make this truly compatible with ReplaySubject you need to take into account also windowTime parameter that specifies "time to live" for each value that goes through this object. This means that whether your cache is empty or not will change in time.
ReplaySubject has method _trimBufferThenGetEvents that does what you need. Unfortunately, this method is private so you need to make a little "hack" in JavaScript and extend its prototype directly.
import { ReplaySubject } from 'rxjs';
// Tell the compiler there's a isNowEmpty() method
declare module "rxjs/ReplaySubject" {
interface ReplaySubject<T> {
isNowEmpty(): boolean;
}
}
ReplaySubject.prototype['isNowEmpty'] = function() {
let events = this._trimBufferThenGetEvents();
return events.length > 0;
};
Then using this ReplaySubject is simple:
let s = new ReplaySubject<number>(1, 100);
s.next(3);
console.log(s.isNowEmpty());
s.next(4);
setTimeout(() => {
s.next(5);
s.subscribe(val => console.log('cached:', val));
console.log(s.isNowEmpty());
}, 200);
setTimeout(() => {
console.log(s.isNowEmpty());
}, 400);
Note that some calls to isNowEmpty() return true, while others return false. For example the last one returns false because the value was invalidated in the meantime.
This example prints:
true
cached: 5
true
false
See live demo: https://jsbin.com/sutaka/3/edit?js,console
You could use .scan() to accumulate your count, and map that to a boolean whether it's nonzero. (It takes a second parameter for a seed value which would make it start with a 0, so it always reflects the current count.)
I've also added a .filter() instead of an if statement to make it cleaner:
let cache = new ReplaySubject<number>(1);
cache
.map((object: T) => 1)
.scan((count: number, incoming: number) => count + incoming, 0)
.map((sum) => sum == 0)
.filter((isEmpty: boolean) => isEmpty)
.subscribe((isEmpty: boolean) => {
console.log("I want to be here!!!");
cache.next(0);
});
You could use takeUntil():
Observable.of(true)
.takeUntil(cache)
.do(isEmpty => {
if (isEmpty) {
console.log("I want to be here!!!");
cache.next(0);
}
})
.subscribe();
However this will just work once.
Another way would be to "null" the cache and initialize it as empty by using a BehaviorSubject:
let cache = new BehaviorSubject<number>(null as any);
...
cache
.do(content => {
if (content == null) {
console.log("I want to be here!!!");
cache.next(0);
}
})
.subscribe();
And of course you could initialize the cache with some default value right away.
startWith
let cache = new ReplaySubject<number>(1);
isEmpty$ = cache.pipe(mapTo(false), startWith(true));
This says:
Whatever the value is emitted by cache - map it to false. (because it isn't empty after an emission)
Start with true if nothing has been emitted yet (because that means it's empty)
Related
I'm making a custom svelte store by wrapping around a svelte writable store.
I want to detect when that store is not subscribed by any component; when the subscription count is 0
My objective is to clear some heavy external resources (websockets) that were tied to the custom store when no one is using it.
Currently, I'm counting the subscriptions and unsubscriptions by wrapping around subscribe( ) method. It works as expected. But It looks like a nasty hack to me.
My question: Is there a standard / clean way to achieve this behavior in Svelte?
If not, can someone with more experience in Javascipt and svelte confirm whether this is legit?
Demo on : https://svelte.dev/repl/f4e24fb5c56f457a94bf9cf645955b9f?version=3.43.1
import { writable } from 'svelte/store';
// Instanciate the store
export let store = MakeStore();
// By design, I want a function that returns a custom svelte store
export function MakeStore(initialValue = null) {
const { subscribe, set, update } = writable(initialValue);
let subscribercount = 0;
let wsubscribe = function (run, callback) {
subscribercount++;
console.log("subscribercount++", subscribercount);
let wunsubscribe = subscribe(run, callback);
return () => {
subscribercount--;
console.log("subscribercount--", subscribercount);
if (subscribercount == 0) {
// -------------------------------
// Free up resources
// I want a clean way to get here
// -------------------------------
console.log("Cleaning up...");
}
return wunsubscribe();
}
}
// Some external calls here
let store = {
subscribe: wsubscribe,
set: newvalue => {
set(newvalue);
// Some external calls here
},
update: update
};
// Some external calls here
return store;
}
Yes, it's built into the store and documented here
from the docs
If a function is passed as the second argument, it will be called when the number of subscribers goes from zero to one (but not from one to two, etc). That function will be passed a set function which changes the value of the store. It must return a stop function that is called when the subscriber count goes from one to zero.
so you would do for example:
const count = writable(0, () => {
console.log('got a subscriber');
return () => console.log('no more subscribers');
});
Update 8 Feb 2023
Note that the above works for both readable and writable stores, in the case of derived stores where you would have the following code:
const count = derived(items, ($items, set) => {
console.log('got a subscriber to a derived store');
return () => console.log('no more subscribers to derived store');
});
Here it will log no more subscribers to derived store, when either the number of subscribers drops to 0 or when the original store changes (this is because this entire function ($items, set) => {...} runs again).
As of v3.55.1 there is no built in way to circumvent this.
can someone advise if there is a better/elegant way of writing the following code to return an observable. That one observable I have an if condition for is optional.
Thanks!
addPoints(
pointsDTO: PointsDTO,
userId: string
): Observable<profile> {
let addPoints$: Observable<profile>, deletePoints$: Observable<profile>;
addPoints$ = this.applyPoints(userId, pointsDTO);
deletePoints$ = this.removePoints(userId, pointsDTO);
if (deletePoints$) {
return concat(deletePoints$, addPoints$).pipe(
switchMap(() => this.getProfile(userId).pipe(catchError()))
),
} else {
return addPoints$.pipe(
switchMap(() => this.getProfile(userId).pipe(catchError())
),
}
}
You can use EMPTY that won't affect concat() functionality. EMPTY just emits one complete notification and no nexts so concat() will just continue with the next source Observable:
return concat(deletePoints$ || EMPTY, addPoints$).pipe(
switchMap(() => this.getProfile(userId).pipe(catchError())),
);
RxJS steams involve emitting items over time. That means that you can't if them after they are set to determine if they have a value. Instead, you need to pipe them through a set of operators.
Try something like this:
addPoints$ = this.applyPoints(userId, pointsDTO);
deletePoints$ = this.removePoints(userId, pointsDTO);
return merge(deletePoints$, addPoints$).pipe(
switchMap(() => this.getProfile(userId).pipe(catchError()))
}
This uses merge instead of concat and should work fine if deletePoints$ doesn't emit anything.
But as the other commenter said, I'm not clear on what you are trying to do here, so this may not be the solution you need. Also, I assume there is something subscribing to these streams somewhere?
I have a service managing data fetched from a SocketIO API such as
export class DataService {
private data: SomeData[];
// ...
getData(): Observable<SomeData[]> {
// distinctUntilChanged was used to limit Observable to only emits when data changes
// but it does not seem to change much things...
return this.data.pipe(distinctUntilChanged());
}
// ...
}
and a component calling this service to do
this.banana$ = combineLatest([
someFnToRequestANetworkObject(),
DataService.getData()
]).pipe(
map(([network, data]) => network && data.some(_data=> _data.ip === network.ip))
);
The thing is that each time one of the Observable handled within combineLatest gets emitted, I get to call Array.prototype.some() function. Which I don't want to.
How could I optimize this code so that I don't call some too often ?
One thing to note about distinctUntilChanged() operator is it affects only the subscription. So the operators between the source observable and the subscription are still run. As workaround you could manually check if data has changed between emissions. Try the following
let oldData: any;
this.banana$ = combineLatest([
someFnToRequestANetworkObject(),
DataService.getData()
]).pipe(
map(([network, data]) => {
if (!oldData || oldData !== data) {
oldData = data;
return (network && data.some(_data=> _data.ip === network.ip));
}
return false; // <-- return what you wish when `data` hasn't changed
})
);
I think your hunch about distinctUntilChanged not working is correct.
By default, this operator uses an equality check to determine if two values are the same.
However, this will (in most cases) not work properly if the objects being compared aren't simple scalar values like numbers or booleans.
As it turns out, you can provide your own comparator function as the first argument to distinctUntilChanged.
This will be called on "successive" elements to determine if the new element is different from the most recent one.
The exact definition of this comparator function depends on what your SomeData class/interface looks like, along with what it means for an array of SomeData inhabitants to be the same as another one.
But, as an example, let's say that SomeData looks like this:
interface SomeData {
id: string;
name: string;
age: number;
}
and that SomeData inhabitants are the same if they have the same id.
Furthermore, let's suppose that two arrays of SomeDatas are the same if they contain exactly the same SomeData elements.
Then our comparator function might look like:
function eqSomeDataArrays(sds1: SomeData[], sds2: SomeData[]): boolean {
// Two arrays of `SomeData` inhabitants are the same if...
// ...they contain the same number of elements...
return sds1.length === sds2.length
// ...which are "element-wise", the same (i.e. have the same `id`)
&& sds1.every((sd1, i) => sd1.id === sds2[i].id)
}
To round it all out, your getData method would now look like:
getData(): Observable<SomeData[]> {
return this.data.pipe(distinctUntilChanged(eqSomeDataArrays));
}
in the pipe, after map call, you could use shareReplay(1)
this.banana$ = combineLatest([
someFnToRequestANetworkObject(),
DataService.getData()])
.pipe(
map(([network, data]) => network && data.some(_data=> _data.ip === network.ip)),
shareReplay(1)
);
The task
Suppose we implement an Angular service and need to publish an Observable<number[]> to the world:
numbers: Observable<number[]>;
We want subscribers to:
receive the last value upon subscription
receive the whole modified array every time we change and publish it
Since #1, internally the Observable<number[]> should be "at least" a BehaviorSubject<number[]>.
But what about #2? Let's suppose we need to implement a method publishNumbersChange() which is called whenever we need to change and publish the changed array:
private publishNumbersChange() {
// Get current numbers array
...
changeArray();
// Now publish changed numbers array
...
}
The question
What is the RxJS 5 pattern to implement the task of publishing modified array based on its previous items?
Since I'm asking it mainly because currently I'm doing Angular stuff, here is the second part of the question:
What code does Angular (and the like frameworks based on RxJS) use when they provide an Observable which type parameter is an array to subsequently publish updated array?
Do they just keep a copy of the currently published array separately?
Some thoughts
It seems that storing the underlying array separately, so we always have access to it, is the simplest thing. But at the same time it does not look like a RxJS way (need to have a state outside a RxJS stream).
On the other hand, we could do something like the following:
private publishNumbersChange() {
// To get the latest value from the stream, we have to subscribe
const subscription: Subscription = this.numbers.subscribe((numbers: number[]) => {
// We got the last value in stream in numbers argument. Now make changes to the array
changeArray();
// And push it back as a new value to the stream
this.numbers.next(numbers);
});
// Also we have to unsubscribe
subscription.unsubscribe();
}
I see here at least one issue (not counting the complexity\reusability): "race condition" between executing subscription callback and unsubscribing. Looking at that code you can't tell for sure whether the callback would be actually executed. So it doesn't look as a proper way of doing this either.
It sounds like the operator you may be looking for is scan.
let arraySubject = new BehaviorSubject([]);
let array$ = arraySubject.scan((fullArray, newValue) => fullArray.concat([newValue]), [])
Scan accumulates values over time in an observable stream, and each item in the stream gets the last emitted value and the current value as parameters. executes a function on them and then emits the result. the above example takes a new value and appends it to your full array, the second parameter initializes it to an empty array.
This is clearly kind of restricting though since it only does ONE thing, which may not be robust enough. in this case you need to get clever:
let arraySubject = new BehaviorSubject([]);
let array$ = arraySubject.scan((fullArray, {modifier, payload}) => modifier(fullArray, payload), []);
Now you're passing in an "action" which has a modifier function, which defines how you want to modify the full array, and a payload of any additional data the modifier might need to go into the modifier function along with the full array
so you might do:
let modifier = (full, item) => full.splice(full.indexOf(item), 1);
arraySubject.next({modifier, payload: itemToRemove});
which removes the item you sent through. You can extend this pattern to literally any array modification.
A "gotcha" with scan though is that subscribers only get the accumulated value from the TIME THEY SUBSCRIBED. So, this will happen:
let arraySubject = new BehaviorSubject([]);
let array$ = arraySubject.scan((fullArray, {modifier, payload}) => modifier(fullArray, payload), []);
let subscriber1 = array$.subscribe();
//subscriber1 gets []
let modifier = (full, val) => full.concat([val]);
arraySubject.next({modifier, payload:1});
//subscriber1 gets [1]
arraySubject.next({modifier, payload:2});
//subscriber1 gets [1,2]
let subscriber2 = array$.subscribe();
//subscriber2 gets [2]
arraySubject.next({modifier, payload:3});
//subscriber1 gets [1,2,3]
//subscriber2 gets [2,3]
See what happened there? the only thing stored in the behaviorsubject was the second event, not the full array, scan is storing the full array, so the second subscriber only gets the second action since it wasn't subscribed during the 1st action. So you need a persistent subscriber pattern:
let arraySubject = BehaviorSubject([]);
let arrayModifierSubject = new Subject();
arrayModifierSubject.scan((fullArray, {modifier, payload}) => modifier(fullArray, payload), []).subscribe(arraySubject);
and you modify by calling next on arrayModifierSubject:
let modifier = (full, val) => full.concat([val]);
arrayModifierSubject.next({modifier, payload: 1});
and your subscribers get the array from the array source:
subscriber1 = arraySubject.subscribe();
In this set up, all array modifications go through the modifier subject who in turns broadcasts it to the behaviorsubject who stores the full array for future subscribers and broadcasts it to current subscribers. The behaviorsubject (the store subject) is persistently subscribed to the modifier subject (the action subject), and is the ONLY subscriber to the action subject, so the full array is never lost as the entire history of actions is always maintained.
some sample usages (with the above set up):
// insert 1 at end
let modifier = (full, value) => full.concat([value]);
arrayModifierSubject.next({modifier, payload: 1});
// insert 1 at start
let modifier = (full, value) => [value].concat(full);
arrayModifierSubject.next({modifier, payload: 1});
// remove 1
let modifier = (full, value) => full.splice(full.indexOf(value),1);
arrayModifierSubject.next({modifier, payload: 1});
// change all instances of 1 to 2
let modifier = (full, value) => full.map(v => (v === value.target) ? value.newValue : v);
arrayModifierSubject.next({modifier, payload: {target: 1, newValue: 2}});
you can wrap any of these functions in a "publishNumbersChange" function. How you exactly implement this depends on your needs, you can make functions like:
insertNumber(numberToInsert:number) => {
let modifier = (full, val) => full.concat([val]);
publishNumbersChange(modifier, numberToInsert);
}
publishNumbersChange(modifier, payload) => {
arrayModifierSubject.next({modifier, payload});
}
or you can declare an interface and make classes and use that:
publishNumbersChange({modifier, payload}) => {
arrayModifierSubject.next({modifier, payload});
}
interface NumberArrayModifier {
modifier: (full: number[], payload:any) => number[];
payload: any;
}
class InsertNumber implements NumberArrayModifier {
modifier = (full: number[], payload: number): number[] => full.concat([payload]);
payload: number;
constructor(numberToInsert:number) {
this.payload = numberToInsert;
}
}
publishNumbersChange(new InsertNumber(1));
And you can also extend similar functionality to any array modification. One last protip: lodash is a huge help with defining your modifiers in this type of system
so, how might this look in an angular service context?
This is a very simple implementation that isn't highly reusable, but other implementations could be:
const INIT_STATE = [];
#Injectable()
export class NumberArrayService {
private numberArraySource = new BehaviorSubject(INIT_STATE);
private numberArrayModifierSource = new Subject();
numberArray$ = this.numberArraySource.asObservable();
constructor() {
this.numberArrayModifierSource.scan((fullArray, {modifier, payload?}) => modifier(fullArray, payload), INIT_STATE).subscribe(this.numberArraySource);
}
private publishNumberChange(modifier, payload?) {
this.numberArrayModifierSource.next({modifier, payload});
}
insertNumber(numberToInsert) {
let modifier = (full, val) => full.concat([val]);
this.publishNumberChange(modifier, numberToInsert);
}
removeNumber(numberToRemove) {
let modifier = (full, val) => full.splice(full.indexOf(val),1);
this.publishNumberChange(modifier, numberToRemove);
}
sort() {
let modifier = (full, val) => full.sort();
this.publishNumberChange(modifier);
}
reset() {
let modifier = (full, val) => INIT_STATE;
this.publishNumberChange(modifier);
}
}
Usage here is simple, subscribers just subscribe to numberArray$ and modify the array by calling functions. You use this simple pattern to extend functionality however you like. This controls access to your number array and makes sure it is always modified in ways defined by the api and your state and your subject are always one in the same.
OK but how is this made generic/reusable?
export interface Modifier<T> {
modifier: (state: T, payload:any) => T;
payload?: any;
}
export class StoreSubject<T> {
private storeSource: BehaviorSubject<T>;
private modifierSource: Subject<Modifier<T>>;
store$: Observable<T>;
publish(modifier: Modifier<T>): void {
this.modifierSource.next(modifier);
}
constructor(init_state:T) {
this.storeSource = new BehaviorSubject<T>(init_state);
this.modifierSource = new Subject<Modifier<T>>();
this.modifierSource.scan((acc:T, modifier:Modifier<T>) => modifier.modifier(acc, modifier.payload), init_state).subscribe(this.storeSource);
this.store$ = this.storeSource.asObservable();
}
}
and your service becomes:
const INIT_STATE = [];
#Injectable()
export class NumberArrayService {
private numberArraySource = new StoreSubject<number[]>(INIT_STATE);
numberArray$ = this.numberArraySource.store$;
constructor() {
}
insertNumber(numberToInsert: number) {
let modifier = (full, val) => full.concat([val]);
this.numberArraySource.publish({modifier, payload: numberToInsert});
}
removeNumber(numberToRemove: number) {
let modifier = (full, val) => full.splice(full.indexOf(val),1);
this.numberArraySource.publish({modifier, payload: numberToRemove});
}
sort() {
let modifier = (full, val) => full.sort();
this.numberArraySource.publish({modifier});
}
reset() {
let modifier = (full, val) => INIT_STATE;
this.numberArraySource.publish({modifier});
}
}
How do I wait for one stream (say, StreamA) to return non-null value and then invoke StreamB subscribe function. I'm not particularly interested in StreamA's value. In turn, I am trying to get StreamB's value which might have been updated before the StreamA returned non-null value, and might not have any new events.
I tried, pausable, by looking at this: RxJS: How can I do an "if" with Observables?, but unfortunately could not get it to work. This is because, there are no exported class pausable, rxjs v 5.0.0-beta.6.
This is how far, I've come up with, as per the answer.
export class AuthService {
userModel: FirebaseListObservable = this.af.database.list(/users);
constructor(private af: AngularFire) {
var user = this.currentAuthor();
var userStream = user;
this.af.auth.flatMap((d) => { console.log(d);return this.userModel.publishReplay(1); });
this.userModel
.subscribe((data) => {
var flag = false;
data.forEach((item) => {
if (item.$key && item.$key === user.uid) {
flag = true;
return;
}
});
if (flag) {
console.log('hello');
} else {
this.userModel.push({
firstName: user.auth.displayName.substr(0, user.auth.displayName.lastIndexOf(' ')),
lastName: user.auth.displayName.substr(user.auth.displayName.lastIndexOf(' '), user.auth.displayName.length),
displayPic: user.auth.photoURL,
provider: user.provider,
uid: user.uid
}
);
}
})
}
public currentAuthor():FirebaseAuthState {
return this.af.auth.getAuth();
}
Hope, I can make myself clear. Even I am getting confused now. :p.
I am new to rxjs and reactive programming. And, any help will be appreciated.
And, btw, thanks for stopping by. :)
I suppose by plausible you mean pausable? I am not sure what exactly you are trying to achieve here (control flow?). However, if you want streamB value after streamA produces a value, then you can use flatMap.
streamA.flatMapLatest(function (_){return streamB})
That should give you, anytime streamA emits, the values emitted after that time by streamB.
If you want values including the last one B emitted prior to that time, you can use streamBB = streamB.publishReplay(1) and
streamA.flatMapLatest(function (_){return streamBB})
Haven't tested it, so keep me updated if that works.