rxjs observe array push - javascript

I want to monitor when an object is pushed onto an array using observables. I want to start with an empty array and when a push occurs I want the observable to detect and process it and then wait till the next push. This would be very similar to "fromEvent" where the observable waits for the event. The code below immediately calls completed() because the array is empty, how do I make it wait for a push?
var testArray = [];
test(){
var o = {timestamp: new Date()}
testArray.push(o)
}
var o = Observable
.from(testArray)
.concatMap( x => {
return x;
});
o.subscribe(
x => { console.log("onNext x=",x.timestamp) },
e => console.log('onError:', e),
() => {console.log('onCompleted');} );
Note: The input mechanism does not have to be an array. Any type of message queue object will work for me.

If all you're trying to do is create an Observable that you can 'push' values into, I recommend using an RXJS Subject.
i.e.
const date$ = new Rx.Subject();
date$.next(new Date());
Now you have an Observable stream of Date objects that you can "push" to with the next() method.
If you really need to have an intermediate (non-Observable) data type for your queue, then I recommend using a new ES6 feature, proxies.
const queue = new Proxy([], {
set: function(obj, prop, value) {
if (!isNaN(prop)) {
date$.next(value)
}
obj[prop] = value
return true
},
})
Now you have an array that is proxied so that any time a value is added to it, it will be added to your Observable stream.

You could subclass Array and implement some kind of notification mechanism to tell you when pushes happen (this is really bare bones):
class CustomArray extends Array {
push(e) {
super.push(e)
if (this._listeners) {
this._listeners.forEach(l => l(e))
}
}
addPushListener(listener) {
this._listeners = this._listeners || []
this._listeners.push(listener)
}
removePushListener(listener) {
if (this._listeners) {
const index = this._listeners.indexOf(listener)
if (index >= 0) {
this._listeners.splice(index, 1)
}
}
}
}
Then with a function you could wrap this into an Observable
const observePushes = array => Rx.Observable.fromEventPattern(
array.addPushListener.bind(array),
array.removePushListener.bind(array)
)
Then you would be able to subscribe to changes and unsubscribe whenever you want, like with any other observable.
const arr = new CustomArray()
const pushObservable = observePushes(arr)
const subscription = pushObservable.subscribe(e => console.log(`Added ${e}`))
arr.push(1)
arr.push(2)
arr.push(3)
arr.push("a")
subscription.dispose()
arr.push("b")
Also mind that this Observable never really completes, since at no point in time can you guarantee that nothing more will be added to an array.
A fiddle: http://jsfiddle.net/u08daxdv/1/

Related

How to get the observable value inside a .map() function

I have a function _populateData that creates a new list of properties from another list of properties.
There is an observable getOwnerForProperty that returns the owner's value.
//Get single owner observable
public getOwnerForProperty(prop: any){
return this._manageOwnerService._getOwnersOfProperty(prop).pipe(map(o => o[0]))
How can I call the observable from within the .map() function to obtain the observable's value and attach it to the new object as seen below?
In my opinion, it would not be a good idea to subscribe getOwnerForProperty function in the .map(). Please advise on the best way to approach this following best practices.
/**
* Returns the active properties data.
*
* #param props - The property list.
* #returns An array of properties
*/
private _populateData(props: Property[]) {
return
const populated = props
.filter(prop => !prop.isArchived)
.map((p: Property) => {
// refactoring here
this.getOwnerForProperty(p).pipe(
map((owner: Owner) => {
const obj = {
propertyName: p.info.name.toUpperCase(),
owner: owner.name,
createdOn: p.createdOn ? __FormatDateFromStorage(p.createdOn) : ''
}
return obj;
})
)
}
)
return populated;
}
}
It's not entirely clear from your question what exactly you are trying to achieve, but here is my solution, so you will hopefully get the idea:
filter for the properties you want to "enrich".
use forkJoin to create an array of observables and wait for all of them to complete.
map each property to the observable you want to wait for.
map the result of the observable to the initial property and enrich it with the owner object.
forkJoin returns an observable which will basically emit a single array of enriched objects and complete. If you wish to await this, you can wrap this in lastValueFrom operator, like await lastValueFrom(forkJoin(...))
function _populateData(props: Property[]) {
const propertiesToPopulate = props.filter((prop) => !prop.isArchived);
forkJoin(
propertiesToPopulate.map((p: Property) => {
return getOwnerForProperty(p).pipe(
map((owner) => ({
...p,
owner,
}))
);
})
);
}

How to serialize execution of array of observables

I have a validation process which validates data in the table row by row. Because each row validation uses a shared resource, access to it must be serialized.
public validate():Observable<boolean>{
const rowValidations:Observable<boolean>[] = dataRows.map(row=>this.validateSingleRow(row);
return forkJoin(...rowValidations).pipe(
map(results=>results.every(r=>r))
)
}
If I understand correctly, forkJoin will not wait for each observable to finish before subscribing to the next one like concat would so that will probably fail. concat on the other hand serializes all the observables into a single stream.
How can I get a subscription order like with concat but have an array of results of each observable like with forkJoin effectively synchronizing execution of each inner observable (like Javas synchronzied validateSingleRow)?
Actually, if you know that each this.validateSingleRow(row) will always emit only once you can use toArray():
concat(...rowValidations).pipe(
toArray(),
);
concat will guarantee correct order and toArray() will collect all emissions into a single array and reemit it after the source Observable completes.
Otherwise, if validateSingleRow might emit multiple times and you always want only its last value you could use scan:
const indexedRowValidations = rowValidations.map((o, index) => o.pipe(
map(result => [index, result]),
));
concat(...indexedRowValidations ).pipe(
scan((acc, [index, result]) => {
acc[index] = result;
return acc;
}, {}),
takeLast(1),
);
(I didn't test it but I believe you get the idea :)).
Would something like this do the trick for you?
class SomeClass {
dataRows = [1, 2, 3];
public validate(): Observable<boolean[]> {
return this.resolveSequentially(this.dataRows);
}
private validateSequentially<T>([cur, ...obs]: T[]): Observable<boolean[]> {
return cur
? this.validateSingleRow(cur).pipe(
switchMap((x) =>
this.validateSequentially(obs).pipe(map((arr) => [x, ...arr]))
)
)
: of([]);
}
// Mock
private validateSingleRow(cur: any) {
console.log(`Validating ${cur}...`);
return of(Math.floor(Math.random() * 2) === 1).pipe(
delay(1000),
tap((x) => console.log(`Result: ${x}`))
);
}
}
const obj = new SomeClass();
obj.validate().subscribe(console.log);
StackBlitz demo
Solution that meets my requirement is simpler than one might think. I have used concat with toArray() like this
const rowValidations:Observable<boolean>[] = dataRows.map(row=>defer(()=>this.validateSingleRow(row));
return concat(...rowValidations).pipe(
toArray(),
map(results=>results.every(r=>r))
)
so validateSingleRow is executed one by one and toArray transforms boolean stream into array of boolean.

When pushing a new value inside an array it gets totally override - VUEX

Hello so I am creating a filter search and I 'm trying to collect all the key (tags) that the user press, inside an array, however every time that a new value is push it does override the entire array. So I tried a couple of things, like spread syntax, concat, etc. But with no luck.
So my action looks like this:
const setCurrentFilters = async (context, payload) => {
if (payload) {
context.commit('setCurrentFilter');
}
}
My state
state:{
filters: JSON.parse(sessionStorage.getItem('currentFilters') || '[]'),
}
The mutation
setCurrentFilter(state, payload) {
state.filters.push(payload);
sessionStorage.setItem('currentFilters', JSON.stringify(payload));
}
And my getter
currentFilters(state) {
return state.filters;
},
Thank you in advance for any help : )
This is simply because you set const filters = []; which means that the next condition if (filters.length) will always return false (as you just created this array) and therefore the else statement will execute.
in the else statement you basically push the new payload to the empty array you just initialized - which makes your array always hold only the new value
i believe that you just need to remove the const filters = []; line, and access the filters property that exists in your state

Polling with RxJS that can recover missed events

I am trying to use RxJS to poll for events. However, I only have access to one function, which is getEvent(). I can do 2 things with the getEvent function:
getEvent("latest") — this will give me the latest event object
getEvent(eventId) - I pass in an integer and it will give me the event object corresponding to the eventId.
Event IDs always increment from 0, but the problem is, if my polling interval isn't small enough, I might miss events.
For example, if I do a getEvent("latest") and I get an event that has an ID of 1, that's great. But if the next time I call it, I get an ID of 3, I know that I missed an event.
In this case, I want to use a higher-order observable to call getEvent(2) and getEvent(3) so that the consumer of the stream I am creating won't have to worry about missing an event.
Right now, all I have is something like this:
timer(0, 500).pipe(
concatMap(() => from(getEvent("latest"))
)
For some context, I'm working off of this blogpost: https://itnext.io/polling-using-rxjs-b56cd3531815
Using expand to recursively call GET fits here perfectly. Here is an example with DEMO:
const source = timer(0, 2000)
const _stream = new Subject();
const stream = _stream.asObservable();
const s1 = source.pipe(tap(random)).subscribe()
const sub = stream.pipe(
startWith(0),
pairwise(),
concatMap((v: Array<number>) => {
let missing = v[1] - v[0];
return missing ? getMissing(v[0], missing) : EMPTY
})
).subscribe(console.log)
function getMissing(start, count) {
return getById(start).pipe(
expand(id => getById(id+1)),
take(count)
)
}
// helper functions for DEMO
let i = 1;
function random() {. // THIS IS YOUR getEvent('latest')
if (i < 10) {
i+=2;
_stream.next(i
// (Math.floor(Math.random() * 8))
)
}
}
function getById(id) {. // THIS IS YOUR getEvent(eventId)
return of(id).pipe(delay(1000)) // delay to mimic network
}

RxJS, how to poll an API to continuously check for updated records using a dynamic timestamp

I am new to RxJS and I am trying to write an app that will accomplish the following things:
On load, make an AJAX request (faked as fetchItems() for simplicity) to fetch a list of items.
Every second after that, make an AJAX request to get the items.
When checking for new items, ONLY items changed after the most recent timestamp should be returned.
There shouldn't be any state external to the observables.
My first attempt was very straight forward and met goals 1, 2 and 4.
var data$ = Rx.Observable.interval(1000)
.startWith('run right away')
.map(function() {
// `fetchItems(modifiedSince)` returns an array of items modified after `modifiedSince`, but
// I am not yet tracking the `modifiedSince` timestamp yet so all items will always be returned
return fetchItems();
});
Now I'm excited, that was easy, it can't be that much harder to meet goal 3...several hours later this is where I am at:
var modifiedSince = null;
var data$ = Rx.Observable.interval(1000)
.startWith('run right away')
.flatMap(function() {
// `fetchItems(modifiedSince)` returns an array of items modified after `modifiedSince`
return fetchItems(modifiedSince);
})
.do(function(item) {
if(item.updatedAt > modifiedSince) {
modifiedSince = item.updatedAt;
}
})
.scan(function(previous, current) {
previous.push(current);
return previous;
}, []);
This solves goal 3, but regresses on goal 4. I am now storing state outside of the observable.
I'm assuming that global modifiedSince and the .do() block aren't the best way of accomplishing this. Any guidance would be greatly appreciated.
EDIT: hopefully clarified what I am looking for with this question.
Here is another solution which does not use closure or 'external state'.
I made the following hypothesis :
fetchItems returns a Rx.Observable of items, i.e. not an array of items
It makes use of the expand operator which allows to emit values which follow a recursive relationship of the type x_n+1 = f(x_n). You pass x_n+1 by returning an observable which emits that value, for instance Rx.Observable.return(x_n+1) and you can finish the recursion by returning Rx.Observable.empty(). Here it seems that you don't have an ending condition so this will run forever.
scan also allows to emit values following a recursive relationship (x_n+1 = f(x_n, y_n)). The difference is that scan forces you to use a syncronous function (so x_n+1 is synchronized with y_n), while with expand you can use an asynchronous function in the form of an observable.
Code is not tested, so keep me updated if this works or not.
Relevant documentation : expand, combineLatest
var modifiedSinceInitValue = // put your date here
var polling_frequency = // put your value here
var initial_state = {modifiedSince: modifiedSinceInitValue, itemArray : []}
function max(property) {
return function (acc, current) {
acc = current[property] > acc ? current[property] : acc;
}
}
var data$ = Rx.Observable.return(initial_state)
.expand (function(state){
return fetchItem(state.modifiedSince)
.toArray()
.combineLatest(Rx.Observable.interval(polling_frequency).take(1),
function (itemArray, _) {
return {
modifiedSince : itemArray.reduce(max('updatedAt'), modifiedSinceInitValue),
itemArray : itemArray
}
}
})
You seem to mean that modifiedSince is part of the state you carry, so it should appear in the scan. Why don-t you move the action in do into the scan too?. Your seed would then be {modifiedSince: null, itemArray: []}.
Errr, I just thought that this might not work, as you need to feed modifiedSince back to the fetchItem function which is upstream. Don't you have a cycle here? That means you would have to use a subject to break that cycle. Alternatively you can try to keep modifiedSince encapsulated in a closure. Something like
function pollItems (fetchItems, polling_frequency) {
var modifiedSince = null;
var data$ = Rx.Observable.interval(polling_frequency)
.startWith('run right away')
.flatMap(function() {
// `fetchItems(modifiedSince)` returns an array of items modified after `modifiedSince`
return fetchItems(modifiedSince);
})
.do(function(item) {
if(item.updatedAt > modifiedSince) {
modifiedSince = item.updatedAt;
}
})
.scan(function(previous, current) {
previous.push(current);
return previous;
}, []);
return data$;
}
I have to run out to celebrate the new year, if that does not work, I can give another try later (maybe using the expand operator, the other version of scan).
How about this:
var interval = 1000;
function fetchItems() {
return items;
}
var data$ = Rx.Observable.interval(interval)
.map(function() { return fetchItems(); })
.filter(function(x) {return x.lastModified > Date.now() - interval}
.skip(1)
.startWith(fetchItems());
That should filter the source only for new items, plus start you off with the full collection. Just write the filter function to be appropriate for your data source.
Or by passing an argument to fetchItems:
var interval = 1000;
function fetchItems(modifiedSince) {
var retVal = modifiedSince ? items.filter( function(x) {return x.lastModified > modifiedSince}) : items
return retVal;
}
var data$ = Rx.Observable.interval(interval)
.map(function() { return fetchItems(Date.now() - interval); })
.skip(1)
.startWith(fetchItems());

Categories