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
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` )
})
})
})
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)
);
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.
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))))
);
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')
);