I have a ReplaySubject that accumulate data with scan operator and every 10000 ms should be reset. Is there any another way to do it?
Now:
let subject = new ReplaySubject();
subject.scan((acc, cur) => {
acc.push(cur);
return acc;
}, [])
.subscribe(events => {
localStorage.setItem('data', JSON.stringify(events))
});
subject
.bufferTime(10000)
.map(() => {
subject.observers[0]._seed = [];
})
.subscribe(() => localStorage.removeItem('data'));
I asked a very similar question few days ago and later answered myself
accumulating values such as with scan but with the possibility to reset the accumulator over time
maybe this can help you
SOME MORE DETAILS
An alternative approach is to have an Observable which acts as a timer which emits at a fixed interval, 10000ms in your case.
Once this timer emits, you pass the control to the Observable that cumululates via scan operator. To pass the control you use the switchMap operator to make sure the previous instance of the Observable completes.
If I understand correctly what you want to achieve, I would use a normal Subject rather than ReplaySubject.
The code could look something like this
const subject = new Subject<number>();
const timer = Observable.timer(0, 1000).take(4);
const obs = timer.switchMap(
() => {
console.log('-----');
return subject
.scan((acc, cur) => {
acc.push(cur);
return acc;
}, []);
}
)
obs.subscribe(
events => {
console.log(JSON.stringify(events))
}
);
// TEST DATA EMITTED BY THE SUBJECT
setTimeout(() => {
subject.next(1);
}, 100);
setTimeout(() => {
subject.next(2);
}, 1100);
setTimeout(() => {
subject.next(3);
}, 2100);
setTimeout(() => {
subject.next(4);
}, 2200);
Related
I have the next example
import { Subject } from "rxjs";
const subject = new Subject();
subject.subscribe(() => new Promise(res => {
setTimeout(() => console.log('!! 1'), 500);
}))
subject.subscribe(() => new Promise(res => {
setTimeout(() => console.log('!! 2'), 1000);
}))
console.log('>>> START')
subject.next();
console.log('<<< FINISH')
Console looks like
>>> START
<<< FINISH
!! 1
!! 2
I want the following behavious
>>> START
!! 1
!! 2
<<< FINISH
Can I reach expected behaviour or I should to use another aproach?
Ok so, this should work. I just forced the behavior of the Observables. To be clear, observables are async javascript and need to share values in async mode so that you can 'emit' a new value through a subject and all the observables can see that value while doing some other tasks. This code do exactly what you asked for but it makes no sense to emit a value and await for that value in the same place and moment, to achieve that you should probably think about another way of coding this module.
Said that, i've tested this snippet and it works, hope this will help
import { Subject } from "rxjs";
const subject = new Subject();
async function nextValue(value) {
return new Promise((resolve, reject) => {
subject.subscribe(subValue => {
setTimeout(() => {
console.log(subValue);
resolve();
}, 500);
}, err => reject(err));
subject.next(value);
});
}
(async () => {
console.log('>>> START');
await nextValue('myValue');
console.log('<<< FINISH');
})();
I perform http requests to my db and have noticed that if I send all the requests at once, some of them will get a timeout errors. I'd like to add a delay between calls so the server doesn't get overloaded. I'm trying to find the RxJS solution to this problem and don't want to add a setTimeout.
Here is what I currently do:
let observables = [];
for(let int = 0; int < 10000; int++){
observables.push(new Observable((observer) => {
db.add(doc[int], (err, result)=>{
observer.next();
observer.complete();
})
}))
}
forkJoin(observables).subscribe(
data => {
},
error => {
console.log(error);
},
() => {
db.close();
}
);
You can indeed achieve this with Rxjs quite nicely. You'll need higher order observables, which means you'll emit an observable into an observable, and the higher order observable will flatten this out for you.
The nice thing about this approach is that you can easily run X requests in // without having to manage the pool of requests yourself.
Here's the working code:
import { Observable, Subject } from "rxjs";
import { mergeAll, take, tap } from "rxjs/operators";
// this is just a mock to demonstrate how it'd behave if the API was
// taking 2s to reply for a call
const mockDbAddHtppCall = (id, cb) =>
setTimeout(() => {
cb(null, `some result for call "${id}"`);
}, 2000);
// I have no idea what your response type looks like so I'm assigning
// any but of course you should have your own type instead of this
type YourRequestType = any;
const NUMBER_OF_ITEMS_TO_FETCH = 10;
const calls$$ = new Subject<Observable<YourRequestType>>();
calls$$
.pipe(
mergeAll(3),
take(NUMBER_OF_ITEMS_TO_FETCH),
tap({ complete: () => console.log(`All calls are done`) })
)
.subscribe(console.log);
for (let id = 0; id < NUMBER_OF_ITEMS_TO_FETCH; id++) {
calls$$.next(
new Observable(observer => {
console.log(`Starting a request for ID "${id}""`);
mockDbAddHtppCall(id, (err, result) => {
if (err) {
observer.error(err);
} else {
observer.next(result);
observer.complete();
}
});
})
);
}
And a live demo on Stackblitz: https://stackblitz.com/edit/rxjs-z1x5m9
Please open the console of your browser and note that the console log showing when a call is being triggered starts straight away for 3 of them, and then wait for 1 to finish before picking up another one.
Looks like you could use an initial timer to trigger the http calls. e.g.
timer(delayTime).pipe(combineLatest(()=>sendHttpRequest()));
This would only trigger the sendHttpRequest() method after the timer observable had completed.
So with your solution. You could do the following...
observables.push(
timer(delay + int).pipe(combineLatest(new Observable((observer) => {
db.add(doc[int], (err, result)=>{
observer.next();
observer.complete();
}))
}))
Where delay could probably start off at 0 and you could increase it using the int index of your loop by some margin.
Timer docs: https://www.learnrxjs.io/learn-rxjs/operators/creation/timer
Combine latest docs: https://www.learnrxjs.io/learn-rxjs/operators/combination/combinelatest
merge with concurrent value:
mergeAll and mergeMap both allow you to define the max number of subscribed observables. mergeAll(1)/mergeMap(LAMBDA, 1) is basically concatAll()/concatMap(LAMBDA).
merge is basically just the static mergeAll
Here's how you might use that:
let observables = [...Array(10000).keys()].map(intV =>
new Observable(observer => {
db.add(doc[intV], (err, result) => {
observer.next();
observer.complete();
});
})
);
const MAX_CONCURRENT_REQUESTS = 10;
merge(...observables, MAX_CONCURRENT_REQUESTS).subscribe({
next: data => {},
error: err => console.log(err),
complete: () => db.close()
});
Of note: This doesn't batch your calls, but it should solve the problem described and it may be a bit faster than batching as well.
mergeMap with concurrent value:
Perhaps a slightly more RxJS way using range and mergeMap
const MAX_CONCURRENT_REQUESTS = 10;
range(0, 10000).pipe(
mergeMap(intV =>
new Observable(observer => {
db.add(doc[intV], (err, result) => {
observer.next();
observer.complete();
});
}),
MAX_CONCURRENT_REQUESTS
)
).subscribe({
next: data => {},
error: err => console.log(err),
complete: () => db.close()
});
I'm using RxJS 6 to lazily step through iterable objects using code similar to example running below. This is working well but I'm having trouble solving my final use case.
Full code here
import { EMPTY, defer, from, of } from "rxjs";
import { delay, expand, mergeMap, repeat } from "rxjs/operators";
function stepIterator (iterator) {
return defer(() => of(iterator.next())).pipe(
mergeMap(result => result.done ? EMPTY : of(result.value))
);
}
function iterateValues ({ params }) {
const { values, delay: delayMilliseconds } = params;
const isIterable = typeof values[Symbol.iterator] === "function";
// Iterable values which are emitted over time are handled manually. Otherwise
// the values are provided to Rx for resolution.
if (isIterable && delayMilliseconds > 0) {
const iterator = values[Symbol.iterator]();
// The first value is emitted immediately, the rest are emitted after time.
return stepIterator(iterator).pipe(
expand(v => stepIterator(iterator).pipe(delay(delayMilliseconds)))
);
} else {
return from(values);
}
}
const options = {
params: {
// Any iterable object is walked manually. Otherwise delegate to `from()`.
values: ["Mary", "had", "a", "little", "lamb"],
// Delay _between_ values.
delay: 350,
// Delay before the stream restarts _after the last value_.
runAgainAfter: 1000,
}
};
iterateValues(options)
// Is not repeating?!
.pipe(repeat(3))
.subscribe(
v => {
console.log(v, Date.now());
},
console.error,
() => {
console.log('Complete');
}
);
I'd like to add in another option which will re-execute the stream, an indefinite number of times, after a delay (runAgainAfter). I'm having trouble composing this in cleanly without factoring the result.done case deeper. So far I've been unable to compose the run-again behavior around iterateValues.
What's the best approach to accomplish the use case?
Thanks!
Edit 1: repeat just hit me in the face. Perhaps it means to be friendly.
Edit 2: No, repeat isn't repeating but the observable is completing. Thanks for any help. I'm confused.
For posterity here is the full code sample for a revised edition is repeat-able and uses a consistent delay between items.
import { concat, EMPTY, defer, from, interval, of, throwError } from "rxjs";
import { delay, expand, mergeMap, repeat } from "rxjs/operators";
function stepIterator(iterator) {
return defer(() => of(iterator.next())).pipe(
mergeMap(result => (result.done ? EMPTY : of(result.value)))
);
}
function iterateValues({ params }) {
const { values, delay: delayMilliseconds, times = 1 } = params;
const isIterable =
values != null && typeof values[Symbol.iterator] === "function";
if (!isIterable) {
return throwError(new Error(`\`${values}\` is not iterable`));
}
// Iterable values which are emitted over time are handled manually. Otherwise
// the values are provided to Rx for resolution.
const observable =
delayMilliseconds > 0
? defer(() => of(values[Symbol.iterator]())).pipe(
mergeMap(iterator =>
stepIterator(iterator).pipe(
expand(v => stepIterator(iterator).pipe(delay(delayMilliseconds)))
)
)
)
: from(values);
return observable.pipe(repeat(times));
}
I'm gonna be honest, but there could be better solution for sure. In my solution, I ended up encapsulating delay logic in a custom runAgainAfter operator. Making it an independent part, that doesn't affect your code logic directly.
Full working code is here
And the code of runAgainAfter if anybody needs it:
import { Observable } from "rxjs";
export const runAgainAfter = delay => observable => {
return new Observable(observer => {
let timeout;
let subscription;
const subscribe = () => {
return observable.subscribe({
next(value) {
observer.next(value);
},
error(err) {
observer.error(err);
},
complete() {
timeout = setTimeout(() => {
subscription = subscribe();
}, delay);
}
});
};
subscription = subscribe();
return () => {
subscription.unsubscribe();
clearTimeout(timeout);
};
});
};
Hope it helps <3
I'm working on a service layer that manages subscriptions.
I provide subject-backed observables to consumers like this:
const subject = new Subject();
_trackedSubjects.push(subject);
return subject.asObservable();
Different consumers may monitor the channel, so there may be several observables attached to each subject.
I'd like to monitor the count of subject.observers and if it ever drops back to 0, do some cleanup in my library.
I have looked at refCount, but this only is available on Observable.
I'd love to find something like:
subject.onObserverCountChange((cur, prev) =>
if(cur === 0 && prev !== 0) { cleanUp(subject) }
)
Is there a way to automatic cleanup like this on a subject?
Instead of using Subject - you should probably describe setup/cleanup logic when creating observable. See the example:
const { Observable } = rxjs; // = require("rxjs")
const { share } = rxjs.operators; // = require("rxjs/operators")
const eventSource$ = Observable.create(o => {
console.log('setup');
let i = 0
const interval = setInterval(
() => o.next(i++),
1000
);
return () => {
console.log('cleanup');
clearInterval(interval);
}
});
const events$ = eventSource$.pipe(share());
const first = events$.subscribe(e => console.log('first: ', e));
const second = events$.subscribe(e => console.log('second: ', e));
setTimeout(() => first.unsubscribe(), 3000);
setTimeout(() => second.unsubscribe(), 5000);
<script src="https://unpkg.com/rxjs#6.2.2/bundles/rxjs.umd.min.js"></script>
I just realized that inner-observables (like those defined in a mergeMap or switchMap operator) do not "stop" even when the outer-observable has no subscription left.
For a better example, let's show some code:
const {
Subject,
of: obsOf,
concat: obsConcat,
defer,
} = require("rxjs");
const {
finalize,
mergeMap,
tap,
takeUntil,
} = require("rxjs/operators");
const subject = new Subject();
obsOf(null).pipe(
mergeMap(() =>
obsConcat(
defer(() => {
console.log("side-effect 1");
return obsOf(1);
}),
defer(() => {
console.log("side-effect 2");
return obsOf(2);
}),
defer(() => {
console.log("side-effect 3");
return obsOf(3);
})
)
),
finalize(() => {
console.log("finalized");
})
)
.pipe(
takeUntil(subject),
tap((i) => {
if (i === 2) {
subject.next();
}
})
).subscribe(
(i) => { console.log("next", i); },
(e) => { console.log("error", e); },
() => { console.log("complete"); },
);
// Ouput:
// > side-effect 1
// > next 1
// > side-effect 2
// > complete
// > finalized
// > side-effect 3
The fact that the side-effect 3 line is logged is weird since the outer observable already called finalize.
As all those side-effects are in a defer, they could perfectly be avoided after unsubscription. From my point-of-view, those side-effects provide no value at all.
Any idea why RxJS still execute those ?
This is unfortunately by design (as of RxJS 6) - concat will buffer the observables and will subscribe to each buffered one even after you unsubscribe (if the subscription is closed it will subscribe and immediately unsubscribe).
You have to prevent the observables from getting buffered...
obsOf(null).pipe(
mergeMap(() => obsOf(
defer(() => {
console.log("side-effect 1");
return obsOf(1);
}),
defer(() => {
console.log("side-effect 2");
return obsOf(2);
}),
defer(() => {
console.log("side-effect 3");
return obsOf(3);
})
)),
concatAll(),
finalize(() => {
console.log("finalized");
}),
takeUntil(subject),
tap((i) => {
if (i === 2) {
subject.next();
}
})
).subscribe(
(i) => { console.log("next", i); },
(e) => { console.log("error", e); },
() => { console.log("complete"); },
);
One could think the code above works, but only until you delay one of the observables. Replace obsOf(1) with timer(100).pipe(mapTo(1)); and behavior is exactly the same.
The only workaround is to make sure you are not buffering anything (mean don't use concat* operators) or limit observable production some other way (use separate Subject and control the production manually).