How to execute code after concat operator finish - javascript

I have tasks (observables) in array and want run them sequentially and at the end I want to run some finish code
const { concat, of, tap } = rxjs;
const { delay } = rxjs.operators;
let tasks = [
of(1).pipe(delay(1000)),
of(2).pipe(delay(2000)),
of(3).pipe(delay(1000)),
of(4).pipe(delay(2000)),
];
// In below example, word "Finish" is printed 4 times
// but I want print it only once, at the end.
concat(...tasks)
.pipe(tap(()=> console.log('Finish')))
.subscribe(n => console.log(`result for task ${n}`));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.0/rxjs.umd.min.js" integrity="sha512-v0/YVjBcbjLN6scjmmJN+h86koeB7JhY4/2YeyA5l+rTdtKLv0VbDBNJ32rxJpsaW1QGMd1Z16lsLOSGI38Rbg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
I try to use tap but without success - how to do it?

Tap, how you use, will run on each emited value.
You should use finalize instead.
const { concat, of, finalize } = rxjs;
const { delay } = rxjs.operators;
let tasks = [
of(1).pipe(delay(1000)),
of(2).pipe(delay(2000)),
of(3).pipe(delay(1000)),
of(4).pipe(delay(2000)),
];
concat(...tasks)
.pipe(finalize(()=> console.log('Finish')))
.subscribe(n => console.log(`result for task ${n}`));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.0/rxjs.umd.min.js" integrity="sha512-v0/YVjBcbjLN6scjmmJN+h86koeB7JhY4/2YeyA5l+rTdtKLv0VbDBNJ32rxJpsaW1QGMd1Z16lsLOSGI38Rbg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

Related

How to find the time difference with dayjs in cypress?

I'm trying to find the time taken for an element to load using this code but doesn't work.
Expected Result: Total time taken is 90 seconds(or in milliseconds)
const start = cy.log(dayjs.format("HH:mm.ss.SSS));
const end = cy.log(dayjs.format("HH:mm.ss.SSS));
const diff = dayjs(end).unix() - dayjs(start).unix();
const timetaken = dayjs.utc(diff).format("HH.mm.ss.SSS");
cy.log(timetaken);
It gets a bit tricky because Cypress runs things in a command queue, you need to run (most) dayjs commands in .then() callbacks.
Here's a simple example
import dayjs from 'dayjs'
const duration = require('dayjs/plugin/duration')
dayjs.extend(duration)
it('times loading a site and selecting an element', () => {
const start = dayjs();
let end;
cy.visit('http://example.com')
cy.get('h1').then(() => {
// ensure end is set only after get command finishes
// by using a .then()
end = dayjs();
cy.log(`start: ${start.format("HH:mm.ss.SSS")}`)
cy.log(`end: ${end.format("HH:mm.ss.SSS")}`)
cy.log(`diff: ${dayjs.duration(end.diff(start)).$ms} ms` )
})
})
If you want to do some more testing steps before diffing, you can use Cypress aliases to keep a hold of the start and end.
import dayjs from 'dayjs'
const duration = require('dayjs/plugin/duration')
dayjs.extend(duration)
it('times loading a site using aliases', () => {
cy.wrap(dayjs()).as('start')
cy.visit('http://example.com')
cy.get('h1').then(() => {
cy.wrap(dayjs()).as('end'); // must wrap "end" inside a .then()!
})
// other test stuff here
cy.get('#start').then(start => {
cy.get('#end').then(end => {
cy.log(`start: ${start.format("HH:mm.ss.SSS")}`)
cy.log(`end: ${end.format("HH:mm.ss.SSS")}`)
cy.log(`diff: ${dayjs.duration(end.diff(start)).$ms} ms` )
})
})
})

Angular 6 run code every 5 seconds but it emits for 1st time

I want to run my code as soon as it gets into my component and repeat the logic every 5 seconds
Here is my code, I used the interval of rxjs
ngOnInit() {
const ticker = interval(5000);
ticker.subscribe(() => {
this.calculateTime();
});
}
But the issue here is that for the code runs
1st time at 5s
2nd time at 10s
But I want it to run as
1st time at 0s
2nd time at 5s
3rs time at 10s
This is how I tacked:
ngOnInit() {
const ticker = interval(5000);
this.calculateTime();
ticker.subscribe(() => {
this.calculateTime();
});
}
If you notice I called this.calculateTime(); twice once before ticker and 2nd inside ticker.
Is there a better solution using interval ? If not may be an alternative to interval
You can use timer. It does the same as interval but adds the functionality you need: controlling the first emission.
ngOnInit() {
const ticker = timer(0, 5000);
ticker.subscribe(() => {
this.calculateTime();
});
}
Try the following:
import { startWith } from 'rxjs/operators';
// ....
const ticker = interval(5000).pipe(
startWith(0)
);

How can I create an eager observable from a hot observable?

AFAIK, observable is lazy.
import * as rxjs from 'rxjs'
const { filter, take, map } = rxjs.operators
function awesomeOpators() {
return take(1);
}
const numbers$ = new rxjs.Subject<number>();
const start$ = numbers$.pipe(
awesomeOpators(),
);
numbers$.next(1);
start$.subscribe((val) => {
// outputs 2
console.log(val)
})
numbers$.next(2)
How can I rewrite awesomeOpators such that beginWithLargeNumbe$ starts with 1?
https://stackblitz.com/edit/rxjs-zqkz7r?file=main.ts
You can use a ReplaySubject instead:
const numbers$ = new rxjs.ReplaySubject<number>();
There's a bunch of other ways as well. You haven't told us which part you can or can't control, though, so this will do.

RXJS Observable stretch

I have a Rx.Observable.webSocket Subject. My server endpoint can not handle messages receiving the same time (<25ms). Now I need a way to stretch the next() calls of my websocket subject.
I have created another Subject requestSubject and subscribe to this.
Then calling next of the websocket inside the subscription.
requestSubject.delay(1000).subscribe((request) => {
console.log(`SENDING: ${JSON.stringify(request)}`);
socketServer.next(JSON.stringify(request));
});
Using delay shifts each next call the same delay time, then all next calls emit the same time later ... thats not what I want.
I tried delay, throttle, debounce but it does not fit.
The following should illustrate my problem
Stream 1 | ---1-------2-3-4-5---------6----
after some operation ...
Stream 2 | ---1-------2----3----4----5----6-
Had to tinker a bit, its not as easy as it looks:
//example source stream
const source = Rx.Observable.from([100,500,1500,1501,1502,1503])
.mergeMap(i => Rx.Observable.of(i).delay(i))
.share();
stretchEmissions(source, 1000)
.subscribe(val => console.log(val));
function stretchEmissions(source, spacingDelayMs) {
return source
.timestamp()
.scan((acc, curr) => {
// calculate delay needed to offset next emission
let delay = 0;
if (acc !== null) {
const timeDelta = curr.timestamp - acc.timestamp;
delay = timeDelta > spacingDelayMs ? 0 : (spacingDelayMs - timeDelta);
}
return {
timestamp: curr.timestamp,
delay: delay,
value: curr.value
};
}, null)
.mergeMap(i => Rx.Observable.of(i.value).delay(i.delay), undefined, 1);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.4.2/Rx.js"></script>
Basically we need to calculate the needed delay between emissions so we can space them. We do this using timestamp() of original emissions and the mergeMap overload with a concurrency of 1 to only subscribe to the next delayed value when the previous is emitted. This is a pure Rx solution without further side effects.
Here are two solutions using a custom stream and using only rxjs-operators - since it looks quite complicated I would not advice you to use this solution, but to use a custom stream (see 1st example below):
Custom stream (MUCH easier to read and maintain, probably with better performance as well):
const click$ = Rx.Observable
.fromEvent(document.getElementById("btn"), "click")
.map((click, i) => i);
const spreadDelay = 1000;
let prevEmitTime = 0;
click$
.concatMap(i => { // in this case you could also use "flatMap" or "mergeMap" instead of "concatMap"
const now = Date.now();
if (now - prevEmitTime > spreadDelay) {
prevEmitTime = now;
return Rx.Observable.of(i); // emit immediately
} else {
prevEmitTime += spreadDelay;
return Rx.Observable.of(i).delay(prevEmitTime - now); // emit somewhere in the future
}
})
.subscribe((request) => {
console.log(`SENDING: ${request}`);
});
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>
<button id="btn">Click me!</button>
Using only RxJS Operators (contains issues, probably shouldn't use):
const click$ = Rx.Observable
.fromEvent(document.getElementById("btn"), "click")
.map((click, i) => i);
click$
// window will create a new substream whenever no click happened for 1001ms (with the spread out
.window(click$
.concatMap(i => Rx.Observable.of(i).delay(1000))
.debounceTime(1001)
)
.mergeMap(win$ => Rx.Observable.merge(
win$.take(1).merge(), // emitting the "first" click immediately
win$.skip(1)
.merge()
.concatMap(i => Rx.Observable.of(i).delay(1000)) // each emission after the "first" one will be spread out to 1 seconds
))
.subscribe((request) => {
console.log(`SENDING: ${request}`);
});
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>
<button id="btn">Click me!</button>
Mark van Straten's solution didn't work completely accurately for me. I found a much more simple and accurate solution based from here.
const source = from([100,500,1500,1501,1502,1503]).pipe(
mergeMap(i => of(i).pipe(delay(i)))
);
const delayMs = 500;
const stretchedSource = source.pipe(
concatMap(e => concat(of(e), EMPTY.pipe(delay(delayMs))))
);

how to buffer the latest value until another value arrive in another sequence by rxjs?

I am trying to use rxjs in my project. I have following sequences, what I expected is that the 1rd sequence will only be handled after a value arrive in another sequence, and only the latest value in the 1st sequence will be reserved. Any suggestion for it?
s1$ |a---b----c-
s2$ |------o----
expected result:
s3$ |------b--c-
I'd combine sample() that is already very similar to what you need and skipUntil().
const start = Scheduler.async.now();
const trigger = new Subject();
const source = Observable
.timer(0, 1000)
.share();
Observable.merge(source.sample(trigger).take(1), source.skipUntil(trigger))
.subscribe(val => console.log(Scheduler.async.now() - start, val));
setTimeout(() => {
trigger.next();
}, 2500);
This will output numbers starting with 2.
source 0-----1-----2-----3-----4
trigger ---------------X---------
output ---------------2--3-----4
Console output with timestamps:
2535 2
3021 3
4024 4
5028 5
Alternatively you could use switchMap() and ReplaySubject but it's probably not as obvious as the previous example and you need two Subjects.
const start = Scheduler.async.now();
const trigger = new Subject();
const source = Observable
.timer(0, 1000)
.share();
const replayedSubject = new ReplaySubject(1);
source.subscribe(replayedSubject);
trigger
.switchMap(() => replayedSubject)
.subscribe(val => console.log(Scheduler.async.now() - start, val));
setTimeout(() => {
trigger.next();
}, 2500);
The output is exactly the same.
I guess I would do this using a ReplaySubject:
const subject$ = new Rx.ReplaySubject(1)
const one$ = Rx.Observable.interval(1000)
const two$ = Rx.Observable.interval(2500)
one$.subscribe(subject$)
const three$ = two$
.take(1)
.flatMap(() => subject$)
// one$ |----0---1---2---3---4---
// two$ |----------0---------1---
// three$ |----------1-2---3---4---
last + takeUntil will work
here is an example:
let one$ = Rx.Observable.interval(1000);
let two$ = Rx.Observable.timer(5000, 1000).mapTo('stop');
one$
.takeUntil(two$)
.last()
.subscribe(
x=>console.log(x),
err =>console.error(err),
()=>console.log('done')
);

Categories

Resources