I found many examples of how to reset timer, but they usually concerned manual reset (e.g. on-click button event).
I need a logic that will automatically reset the value when the countdown ends.
Timer:
type seconds = number;
const getRemainingTime$ = (store: Store): Observable<seconds> => {
// calculate fullTime based on the TriggerDate extracted from the State
// ...
return fullTime$.pipe(
switchMap((fullTime: seconds) =>
timer(0, 1000).pipe(
map((tickCount: number) => fullTime - tickCount),
takeWhile((remainingTime: seconds) => remainingTime >= 0)
)
)
);
}
Trigger (wait for 0 value on timer)
getRemainingTime$(this.store).pipe(
skipWhile((remainingTime: seconds) => remainingTime > 0),
)
.subscribe(data => {
const newTriggerDate: Date = new Date(new Date().getTime() + 60 * 1000); // +60 seconds
this.store.dispatch([new SetTriggerDateAction({ newTriggerDate })]);
});
...and it doesn't work -
When the remaining time is zero, the trigger goes crazy and dispatch an infinite number of actions. What is wrong?
PS: When I manually dispatch SetTriggerDateAction (onClick button), the problem disappears.
It was enough to replace skipWhile to a filter.
skipWhile
All values are emitted if the condition in skipWhile is met at least once.
filter
Only those values specified in the condition are emitted.
Related
I have a costly server ajax request which has one input (full: boolean). If full is false, the server can return either a partial or a full response (response.isFull == true); but if full is true, the server will return a full response. Normally the partial response is good enough, but there are certain conditions that will require a full response. I need to avoid requesting a full response explicitly as much as possible, so I thought I'd start with a BehaviorSubject which I can eventually feed with true and combine it with distinctUntilChanged if I ever need to get the full response. This will give me an observable with false initially and that can give me true if I feed that into it:
const fullSubject = new BehaviorSubject<boolean>(false);
Then I've got a function that takes a boolean parameter and returns an observable with the server request (retried, transformed, etc.). As said, the answer can be partial or full, but it can be full even if the input parameter was false at the server's discretion. For example:
interface IdentityData {
...
isFull: boolean;
}
private getSimpleIdentity(full: boolean): Observable<IdentityData> {
return Axios.get(`/api/identity${full?"?full=true":""}`)
.pipe( ... retry logic ...,
... transformation logic ...,
shareReplay(1) );
}
I need to know how can I combine these so that the following is true:
The server needs to be queried at most twice.
If the first answer is a full answer, no further queries must be performed to the server.
If the first answer is a partial answer, and true is fed into fullSubject, a full answer must be requested.
The expected output from all this is an observable that emits either one full response, or a partial response and, when asked, a full response.
Environment: Vue 2.6.11, RxJS 6.5.5, Axios 0.19.2, TypeScript 3.7.5.
Thanks in advance
Here would be my approach:
const fullSubject = new BehaviorSubject(false);
const src$ = fullSubject.pipe(
switchMap(isFull => Axios.get('...')),
take(2), // Server required at most twice
takeWhile(response => !response.isFull, true), // When `isFull`, it will complete & unsubscribe -> no more requests to the server
shareReplay(1),
);
src$.subscribe(() => { /* ... */ });
function getFullAnswer () {
fullSubject.next(true);
}
takeWhile takes a second argument, inclusive. When set to true, when the predicate function evaluates to false(e.g isFull is true) it will send that value as well. –
if I've got it correctly
private getSimpleIdentity(): Observable<IdentityData> {
return fullSubject.pipe(
switchMap(full => Axios.get(`/api/identity${full ? "?full=true" : ""}`)),
shareReplay(1),
);
}
Uses the retryWhen() operator
const source = of("").pipe(map(() => Math.floor(Math.random() * 10 + 1)));
const example = source
.pipe(
tap((val) => console.log("tap", val)),
map((val) => {
//error will be picked up by retryWhen
if (val !== 5) throw val;
return val;
}),
retryWhen((errors) =>
errors.pipe(
tap(() => console.log("--Wait 1 seconds then repeat")),
delay(1000)
)
)
)
.subscribe((val) => console.log("subscription", val));
/*
output:
tap 3
--Wait 1 seconds then repeat
tap 8
--Wait 1 seconds then repeat
tap 1
--Wait 1 seconds then repeat
tap 4
--Wait 1 seconds then repeat
tap 7
--Wait 1 seconds then repeat
tap 5
subscription 5
*/
I have an endless stream of events and I need to limit them to 5, keep the rest paused for 3 seconds
So need to make a delay after every 5 calls
from([ 1,2,3,4,5,6,7,8,9,11,12,13,14,15,16,17,18,19,21,22,23,24,25,26,27,28 ])
.pipe(
// To demonstrate 1 after 1 values stream we use concatMap
// we return new Observalbe via of operator
// we pipe the delay for each element based on the index value we passed
// in our concatMap
concatMap((x,i) => of(x).pipe(
delayWhen((x) => {
console.log("im index: " + i);
// Not the first element, and every 5th element
return i !== 0 && i % 5 === 0 ? timer(3000): timer(0)})
))
)
.subscribe(x => console.log(x))
// Output: 1,2,3,4,5 ...delay 3s.... 6,7,8,9,10 ...delay 3s...
You can see in this stackblitz I made.
const stream = range(0, 100) // create dataset
.pipe(
bufferCount(5), // slice data into chunks
concatMap( // get this chunk
(msg) => of(msg).pipe(
delay(3000) // and emit every three seconds
))
)
stream.subscribe(item => console.log(item));
I'm trying to measure the duration of 2 actions using redux-observable epic.
There is an answer related but it doesn't really help in my situation.
In redux-observable, how can I measure the epics duration time when running complete?
testeeEpic = action$ => action$.ofType('TEST_START', 'TEST_EMD')
.pipe(
// How to measure duration between 'TEST_START' and 'TEST_EMD'
)
/*
`TEST_START` can be dispatched multiple times before `TEST_EMD`,
duration between the lastest `TEST_START` and `TEST_EMD` is needed.
*/
It would be grateful if someone can help.
Try timeInterval -- it measures time between two emissions.
To measure time between first START and next END:
.pipe(
distinctUntilChanged(),
timeInterval(),
filter(({ value }) => value !== START_EVENT),
map(({ interval }) => interval)
)
Timespan between first START and next END example.
UPDATE
To measure time between latest START and following END:
.pipe(
timeInterval(),
pairwise(),
filter(([first, second]) =>
first.value === START_EVENT
&& second.value === END_EVENT
),
map(([, second]) => second.interval)
)
Timespan between latest START and following END example.
Hope this helps
I have the following, and it does work, it keeps increasing the delay and eventually timing out which is what I wanted.
But because I am using Concatmap i lose the original value from the interval.
let x = 1
let source2$ = interval(500)
.pipe(
concatMap(() => {
x++
let newtime = x * 500
console.log("newtime ", newtime)
return of(5).pipe(delay(newtime))
}),
timeout(3000),
map((data) => {
return 'Source 2: ' + data
})
)
so it prints Source 2: 5.. where as i want it to print the value of the interval.
I got working what i wanted using the concatmap but i think its the wrong operator as I lose the original value.
Can somebody help?
More info
TO summarize, all i would like to do is emit values using the interval and after each emit increase the delay time - eventually it hits the timeout of 3000 ms and errors out.
I've mentioned in comments that you can use concatMap for this that receives ever increasing index from interval:
concatMap(index => {
let newtime = index * 500
console.log("newtime ", newtime)
return of(index).pipe(delay(newtime))
}),
Notice, that I'm returning the value back to the stream by of(index).
I think I understand what were you concerned about returning another Observable. Since you want to emit items in sequence (emit one only after the previous one completes) then you have to use concatMap with another inner Observable. There isn't a special operator only for this functionality because this is "composable behavior" which means you can achieve this behavior by combining existing operators.
const source2$ = interval(500)
.pipe(
map(x => x * 500),
switchMap(x => timer(x)),
timeout(3000),
map(data => 'Source 2: ' + data)
)
UPDATE:
https://stackblitz.com/edit/rxjs-iywcm6?devtoolsheight=60
const source2$ = interval(500)
.pipe(
tap(x => console.log('Tick before delay', x)),
concatMap(x => timer((x + 1) * 500).pipe(mapTo(x))),
tap(x => console.log('Tick after delay', x)),
map(data => 'Source 2: ' + data),
timeout(3000)
).subscribe(
(data) => console.log(data),
e => console.error('Timeout', e))
I am using Observable.interval to schedule code execuiton at specified times:
let obs = Observable.interval(50).subscribe(()=>{
console.log(this.currentFrame+" "+new Date().getTime());
this.currentFrame++
});
This is the output. As you can see, after 6 iterations I already have a 10ms drift. How can I use Observable.interval, but also specify that it needs to recalculate next iteration based on the current drift?
0 1513972329849
1 1513972329901
2 1513972329952
3 1513972330004
4 1513972330057
5 1513972330110
Until #cartant's fix gets repulled, you could use expand and create the behavior yourself. Assuming delay will always drift forward, try the following:
function correcting_interval(interval) {
const start_time = new Date().getTime();
return Observable.of(-1)
.expand(v => Observable.of(v + 1).delay(interval - (new Date().getTime() - start_time) % interval))
.skip(1);
}