I am trying to learn Rxjs and I am seeing some behaviour that I did not expect. The javascript code in question is listed below
function updateText(css_link, observable){
observable.subscribe(x => {
const container = document.querySelector(css_link);
container.textContent = `${x}`;
});
}
function log(observable) {
observable.subscribe(i => {
console.log(i);
});
}
let source = Rx.Observable.timer(0, 1000)
.map(() => {return {value: Math.random()}});
let double = source
.map(x => {return {value: x.value * 2}});
let diff = source
.pairwise()
.map(a => JSON.stringify(a));
updateText("#source", source.map(x => x.value));
updateText("#double", source.map(x => x.value));
updateText("#diff", diff);
It turns out that the output of the double stream are double values of new random numbers, not the random numbers that came from source. When looking at the output of diff I again get the impression that the random numbers are generated independantly in source, double and diff.
I am learning Rxjs and I may be missing a point. I thought that these streams are immutable but that they do depend on one another.
You can find a version of this code on jsbin with some html that is getting updated.
This is because every time you subscribe you're creating a new chain with a new source Observable. This means source, double and diff each one of them has its own timer.
You can see that this is true by printing a message to console every time you're creating a new timer:
let source = Rx.Observable.defer(() => {
console.log('new source');
return Rx.Observable.timer(0, 1000)
.map(() => {return {value: Math.random()}});
});
Now you'll see three messages "new source" in console.
If you want to share a single source Observable you can use multicasting and in particular the share() operator.
let source = Rx.Observable.defer(() => {
console.log('new source');
return Rx.Observable.timer(0, 1000)
.map(() => {return {value: Math.random()}});
}).share();
Now you'll see only one "new source" in console and it should work as you expect.
So your source can look like this:
let source = Rx.Observable.timer(0, 1000)
.map(() => {return {value: Math.random()}})
.share();
Your updated demo: https://jsbin.com/guyigox/3/edit?js,console,output
Related
I'm a functional programming beginner. I'm working on a React Native app using Ramda. The app lets users maintain their houses.
I have written function called asyncPipe which lets me pipe promises and normal functions. I use it for the loginFlow which currently has a http request (getHouseList) as its last function.
const asyncPipe = (...fns) => x => fns.reduce(async (y, f) => f(await y), x);
const loginFlow = asyncPipe(
// ... someFunctions
getHouseList
);
// used later like this in LoginForm.js's handleSubmit():
const list = await loginFlow(credentials);
So, after logging in, the app loads the user's houses. Now depending on whether he has only one or multiple houses I would like to send the user either to list view to choose a house or a detail view if he only has one house. Additionally, I would like to dispatch a Redux action to save the list in my reducer and another action to pick the house if there is only one.
Currently I do it like this:
const list = await loginFlow(credentials);
dispatch(addHouses(list));
if (list.length > 1) {
navigate('ListScreen')
} else {
dispatch(pickHouse(list[0]);
navigate('DetailScreen') ;
}
But as you can see that is super imperative. It seems like I have to 'fork' the list and use it twice in the pipe (because Redux' dispatch does not have a return value).
My main question is:
How to do this more functional / declaratively (if there is a way)?
A little sub question I have would be, whether its okay to be imperative here / if doing it functional is a good idea.
You could probably extend your async pipeline, using something like tap:
const loginFlow = asyncPipe(
// ... some functions
getHouseList,
tap(compose(dispatch, addHouses)),
tap(unless(list => list.length > 1, list => dispatch(pickHouse(list[0])))),
list => navigate(list.length > 1 ? 'ListScreen' : 'DetailScreen', list)
);
Whether this is worth doing will depend upon your application. If the pipeline is already a longish one, then it would probably be cleaner to add things to the end this way, even if they're not particularly functional sections. But for a short pipeline, this might not make much sense.
You also might want to look at the now-deprecated, pipeP or its replacement, pipeWith(then).
But you asked in the title about forking a parameter. Ramda's converge does exactly that:
converge(f, [g, h])(x) //=> f(g(x), h(x))
This allows you to pass more than two functions as well, and to pass more than one parameter to the resulting function:
converge(f, [g, h, i])(x, y) //=> f(g(x, y), h(x, y), i(x, y))
Given that we can use R.then and R.otherwise, then an asyncPipe is not really needed. One of the principle of functional programming is actually delegating orchestration...
Finally, of course you can be more declarative, and a good way to start is trying to avoid imperative control flows. R.ifElse will definitely help you here :)
If your code has side effects,
then use R.tap in your pipes :)
const fake = cb => () => cb([
{ name: 'Hitmands', id: 1 },
{ name: 'Giuseppe', id: 2 },
]);
const fakeApiCall = () => new Promise(resolve => setTimeout(fake(resolve), 500));
const dispatch = action => data => console.log(`dispatch("${action}")`, data);
const navigate = view => data => console.log(`navigate("${view}")`, data);
const loginFlow = (...fns) => R.pipe(
R.tap(() => console.log('login Flow Start')),
fakeApiCall,
R.then(R.pipe(
...fns,
R.tap(() => console.log('login Flow End')),
)),
)
const flow = loginFlow(
R.tap(dispatch('addHouse')), // use tap for side effects
R.ifElse(
R.pipe(R.length, R.gt(R.__, 1)), // result.length > 1
R.tap(navigate('ListScreen')), // onTrue
R.pipe( // onFalse
R.tap(dispatch('pickHouse')),
R.tap(navigate('DetailScreen')),
),
),
);
/* await */ flow();
/** UPDATES **/
const isXGreaterThan1 = R.gt(R.__, 1);
const isListLengthGreatherThanOne = R.pipe(R.length, isXGreaterThan1);
console.log(`is list.length > 1`, isListLengthGreatherThanOne([1, 2, 3]));
console.log(`is list.length > 1`, isListLengthGreatherThanOne([1]));
console.log(`is list.length > 1`, isListLengthGreatherThanOne([]));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
I want to implement a stream object that can do this:
// a -------1------2----3
// map -----\------\----\
// b --------2------4----6
const a = new Stream();
const b = a.map(value => value * 2);
b.subscribe(console.log);
a.push(1);
// 2
a.push(2);
// 4
a.push(3);
// 6
The idea here is that the object b can subscribe new callbacks to stream a. The map function should listen when push is called and apply the mapped out function as well as the originally subscribed one. This is the implementation I have so far:
class Stream {
constructor(queue = []) {
this.queue = queue;
}
subscribe(action) {
if (typeof action === 'function') {
this.queue.push(action);
}
}
map(callback) {
this.queue = this.queue.map(
actionFn => arg => action(callback(arg))
);
return this;
}
push(value) {
this.queue.forEach(actionFn => {
actionFn.call(this, value);
});
}
}
The problem with current implementation is that originally the queue in class Stream is empty so it doesn't go through it. Would appreciate any suggestions or help. I would like to not use any library for this.
Your map needs to create a new Transform stream and return it. Instead of the subscribe you could simply use the standard on('data') event or, better use the read method.
Lastly - you could simply use my work and have your map method already efficiently implemented by using scramjet, which does exactly what you shown above - and moreover it supports async functions. :)
Here's how you'd use it (in some getStream function):
const {DataStream} = require('scramjet');
const stream = new DataStream();
stream.write(1); // you can also use await stream.whenWrote(1);
stream.write(2);
stream.write(3);
return stream.map(x => x * 2);
and then read it somewhere else:
stream.on('data', x => console.log(`x: ${x}`));
// x: 2
// x: 4
// x: 6
Take a look at the scramjet docs here
After such a long time of asking this question I was able to go back to the problem and come up with a simple solution. Since one stream should listen to the one it is subscribed to, we should return the instance of the original in order to preserve the values from the previous stream. Here is the code that I found to be working well:
class Stream {
constructor() {
this.subscriptions = [];
this.mappedActions = [];
}
subscribe(callback) {
this.subscriptions.push(callback);
}
map(actionFunc) {
this.mappedActions.push(actionFunc);
return this;
}
push(opValue) {
this.subscriptions.forEach(cb => {
if (this.mappedActions.length) {
this.mappedActions.forEach(action => {
cb(action.call(this, opValue));
});
} else {
cb(opValue);
}
});
}
}
const a = new Stream();
const b = a.map(value => value * 1 / 2);
const c = b.map(value => value * 3);
c.subscribe(console.log);
c.push(1);
c.push(2);
c.push(3);
// expected output in the console:
// 0.5
// 3
// 1
// 6
// 1.5
// 9
Hope anyone who stumbles upon this interesting problem will find my solution useful. If there is any changes you would like to make, feel free to do so or ping me!
I am trying to figure out, how mergeAll works and created examples:
const clicks = Rx.Observable.interval(4000).map(()=> "first");
const higherOrder = clicks.map((ev) => Rx.Observable.interval(1000).map(() => "inner").take(10));
const firstOrder = higherOrder.mergeAll();
firstOrder.subscribe(x => console.log(x));
the output it always inner and first never outputted. After calling mergeAll() the clicks observable is no more relevant?
On more example:
const input = document.getElementById("window");
const clicks = Rx.Observable.fromEvent(input, 'keyup').map(() => "Hello");
const interval = Rx.Observable.interval(4000);
const result = clicks.window(interval)
.map(win => {
return win.take(1);
})
.mergeAll(); // flatten the Observable-of-Observables
result.subscribe(x => console.log("Result " + x));
on subscribe, I've got the result from outer observable "Result Hello" not the inner observable. What kind of role plays mergeAll in this case?
Why the win variable is an instance observable not Hello?
After calling mergeAll() the clicks observable is no more relevant?
Correct. You map each individual click to a stream of "inner" events. mergeAll simply merges those streams together. The click event lives in this resulting stream only very faintly as the point in time where a specific merged stream starts. It becomes a bit more clear this way:
const clicks$ = Rx.Observable.interval(1000);
const higherOrder$ = clicks$.map(click => Rx.Observable.interval(500)
.map(counter => `${click}–${counter}`)
);
higherOrder$.mergeAll().subscribe(console.log);
The documentation and its marble diagram might also help you understand:
you should use switchMap.
firstOrder.switchMap(x => console.log(x)).subscribe( value => console.log(value));
or
result.switchMap(x => console.log("Result " + x)).subscribe( value => console.log(value));
switch expects a stream of Observables, when it get an Observable pushed onto it’s input stream it unsubscribes from any previous Observables and subscribes to the new one and then emits any values from that Observable onto it’s output stream.
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')
);