Pause, upon resume give last paused value - rxjs

I have a hot Observable fed by a socket. I can use the pausable to pause the socket feed. But once I 'unpause' the observable, I need to display the last values that the socket could have sent while the subscription was paused. I don't want to keep track of the last values the socket sends manually. How could this be pausible?
From the example in the documentation, see comments below:
var pauser = new Rx.Subject();
var source = Rx.Observable.fromEvent(document, 'mousemove').pausable(pauser);
var subscription = source.subscribe(
function (x) {
//somehow after pauser.onNext(true)...push the last socket value sent while this was paused...
console.log('Next: ' + x.toString());
},
function (err) {
console.log('Error: ' + err);
},
function () {
console.log('Completed');
});
// To begin the flow
pauser.onNext(true);
// To pause the flow at any point
pauser.onNext(false);

You don't even need pausable to do this. (Note as well that you tagged RxJS5 but pausable only exists in RxJS 4). You simply need to convert your pauser into a higher order Observable:
var source = Rx.Observable.fromEvent(document, 'mousemove')
// Always preserves the last value sent from the source so that
// new subscribers can receive it.
.publishReplay(1);
pauser
// Close old streams (also called flatMapLatest)
.switchMap(active =>
// If the stream is active return the source
// Otherwise return an empty Observable.
Rx.Observable.if(() => active, source, Rx.Observable.empty())
)
.subscribe(/**/)
//Make the stream go live
source.connect();

Related

Pipe Observable to Subject without making it hot unneccessarily

This question builds upon this one, where it is shown how to feed an Observable into a Subject. My question is similar, but I want to avoid making the Observable hot unless it is necessary, so that it's .pipe() doesn't run needlessly. For example:
const subject$ = new Subject();
const mouseMove$ = Observable.fromEvent(document, 'mousemove')
.pipe(map(it => superExpensiveComputation(it)));
mouseMove$.subscribe(n => subject$.next(n));
Because of the subscription, this will make mouseMove$ hot, and superExpensiveComputation will be run for every mouse move, whether someone is listening for it on subject$ or not.
How can I feed the result of mouseMove$ into subject$ without running superExpensiveComputation unneccessarily?
You can simply use tap instead of subscribe to pass emissions to your subject:
const mouseMove$ = fromEvent(document, 'mousemove').pipe(
map(it => superExpensiveComputation(it)),
tap(subject$)
);
Of course you still need to subscribe to mouseMove$ to make the data flow, but you don't need to have a subscription dedicated to passing the data to your subject.
However, you'll probably want to add share as not to repeat the expensive logic for multiple subscribers.
const mouseMove$ = fromEvent(document, 'mousemove').pipe(
map(it => superExpensiveComputation(it)),
share(),
tap(subject$)
);
But... then in that case, do you really need a subject at all? Unless you are going to be calling .next() from somewhere else, you probably don't.
Because of the subscription, this will make mouseMove$ hot
Wrong. The subscription doesn't change behavior of observable.
Observable.fromEvent(document, 'mousemove') is already hot.
Here's naive cold version of it.
The key takeaway, I have to resubscribe it everytime to get the latest data.
const { of } = rxjs;
const mouseMove$ = of((() => {
let payload = {clientX: 0};
document.addEventListener("mousemove", (ev) => payload.clientX = ev.clientX)
return payload;
})())
let subscriber = mouseMove$.subscribe(pr => console.log(pr));
const loop = () => {
if(subscriber) {
subscriber.unsubscribe();
}
subscriber = mouseMove$.subscribe(pr => console.log(pr));
setTimeout(loop, 1000);
};
setTimeout(loop, 1000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>
and superExpensiveComputation will be run for every mouse move,
whether someone is listening for it on subject$ or not
Then you can get rid of map(it => superExpensiveComputation(it)) and move that lamda to subscription so it still executes on every mouse move but after it has been subscribed..

rxjs switchMap cache the obsolete result and do not create new stream

const s1$ = of(Math.random())
const s2$ = ajax.getJSON(`https://api.github.com/users?per_page=5`)
const s3$ = from(fetch(`https://api.github.com/users?per_page=5`))
const click$ = fromEvent(document, 'click')
click$.pipe(
switchMap(() => s1$)
).subscribe(e => {
console.log(e)
})
I was confused by the code above and can not reason about them properly.
In the first case(s1$), the same result is received every time, it LOOKs fine to me even though I can not understand why switchMap do not start a new stream each time. OK, it is fine
The really wired thing happen when you run s2$ and s3$, the looks equivalent, right? WRONG!!! the behaviours are completely different if you try them out!
The result of s3$ is cached somehow, i.e. if you open the network panel, you will see the http request was send only ONCE. In comparison, the http request is sent each time for s2$
My problem is that I can not use something like ajax from rx directly because the http request is hidden a third-party library, The solution I can come up with is to use inline stream, i.e. create new stream every time
click$.pipe(
switchMap(() => from(fetch(`https://api.github.com/users?per_page=5`)))
).subscribe(e => {
console.log(e)
})
So, how exactly I can explain such behaviour and what is the correct to handle this situation?
One problem is that you actually execute Math.random and fetch while setting up your test case.
// calling Math.random() => using the return value
const s1$ = of(Math.random())
// calling fetch => using the return value (a promise)
const s3$ = from(fetch(`https://api.github.com/users?per_page=5`))
Another is that fetch returns a promise, which resolves only once. from(<promise>) then does not need to re-execute the ajax call, it will simply emit the resolved value.
Whereas ajax.getJSON returns a stream which re-executes every time.
If you wrap the test-streams with defer you get more intuitive behavior.
const { of, defer, fromEvent } = rxjs;
const { ajax } = rxjs.ajax;
const { switchMap } = rxjs.operators;
// defer Math.random()
const s1$ = defer(() => of(Math.random()));
// no defer needed here (already a stream)
const s2$ = ajax.getJSON('https://api.github.com/users?per_page=5');
// defer `fetch`, but `from` is not needed, as a promise is sufficient
const s3$ = defer(() => fetch('https://api.github.com/users?per_page=5'));
const t1$ = fromEvent(document.getElementById('s1'), 'click').pipe(switchMap(() => s1$));
const t2$ = fromEvent(document.getElementById('s2'), 'click').pipe(switchMap(() => s2$));
const t3$ = fromEvent(document.getElementById('s3'), 'click').pipe(switchMap(() => s3$));
t1$.subscribe(console.log);
t2$.subscribe(console.log);
t3$.subscribe(console.log);
<script src="https://unpkg.com/#reactivex/rxjs#6/dist/global/rxjs.umd.js"></script>
<button id="s1">test random</button>
<button id="s2">test ajax</button>
<button id="s3">test fetch</button>

Start stream when another Observable emits its first value

I have two observables, one from key press events and another from ajax requests.
I want the second stream to start when the first stream emits its first value.
var input$ = Rx.Observable.fromEvent( input, 'keydown')
.debounceTime(500)
var countries$ = Rx.Observable.of('https://restcountries.eu/rest/v1/all')
.flatMap(url => $.get( url ))
.retryWhen( errors => {
return errors.scan( (sum, err) => {
if( sum === 2 )
{
throw err;
}
else{
return sum + 1;
}
}, 0).delay(1000)
})
I am using rxjs 5, I want the countries$ observable to start when the input$ observable emits its first value. I tried using skipUntil or debounce passing the input$ Observable but... no luck. I see the ajax request happening in the network tab before I start typing anything. Any idea how I can achieve what I want?
Use switchMap. This will switch from the source stream to the new stream whenever the source Observable emits
var url = 'https://restcountries.eu/rest/v1/all';
var countries$ = input$.switchMap(input => $.get(url))
.retryWhen(...
Note that if a new input arrives before the http request is completed, the old request will be cancelled and a new one issued. So switchMap is perfect if the http request made depends on the specific input (as in a search query)
I used a static URL because that's what your OP uses, but you could easily alter the URL to include a query parameter built from the input.
If you wish to run the http request just once regardless of what the input was, and do not want to cancel the old request when a new input arrives, use take to only read from the source once:
var countries$ = input$.take(1).switchMap(input => $.get(url))
.retryWhen(...

How to cleanly architect downstream subscribers that call an upstream reload in RxJS?

Trying to construct a schedule using RxJS v5, where certain events can trigger the schedule to reload. Currently using 3 sources - schedule$, event$, and userNotification$ (example below).
I've tried a number of different strategies and I'm consistently getting weirdness like recursive reloads when the reloadSchedule event time hits. Is there a way for downstream data (event$) to cleanly trigger upstream (schedule$) reloads, without any actions/notifications lingering from previous schedule items?
schedule$ = new Rx.BehaviorSubject(
{schedule:[
{start:'1pm', end:'2pm', action:'sayhi'},
{start:'2pm', end:'3pm', action:'sayhi'},
{start:'3pm', end:'3pm', action:'reloadSchedule'},
{start:'3:01pm', end:'4pm', action:'sayhi'},
]}
);
function loadSchedule(){
somethingAsync.then((moreData)=>schedule$.next(moreData));
}
event$ = schedule$.flatMap((data)=>{
return Rx.Observable
.from(data.schedule)
.flatMap((event)=>{
return Rx.Observable.timer(event.start)
.flatMap(()=>{
// do actions here once previous actions/notifications finish
if(event.action === 'reloadSchedule'){
loadSchedule()
}
return Rx.Observable.of(someUserMessage);
})
})
})
userNotification$ = Rx.Observable.timer(1000).withLatestFrom(event$)
.flatMap((someUserMessage)={
// fade message after 5 seconds
});
userNotification.subscribe(()=>{});
Ended up figuring out a solution. There may be cleaner ways to do it, but it worked.
Basic idea is to have one timer that controls the actions. Compare the event times to that timer to get the correct current event. Unsubscribe when it's time to reload.
Rough example.
// start and end are ISO strings - showing 1pm etc.
let schedule$ = new Rx.BehaviorSubject([
{start:'1pm', end:'2pm', action:'sayhi'},
{start:'2pm', end:'3pm', action:'sayhi'},
{start:'3pm', end:'3pm', action:'reloadSchedule'},
{start:'3:01pm', end:'4pm', action:'sayhi'},
]);
schedule$.subscribe((sched)=>{
new Scheduler(sched)
});
function loadSchedule(){
somethingAsync.then((moreData)=>schedule$.next(moreData));
}
class Scheduler{
constructor(schedule){
let notificationsCleared = true;
let sliced;
let event$ = Rx.Observable
.timer(1000)
.filter(()=>notificationsCleared)
.map(()=>{
let now = (new Date()).toISOString();
sliced || (sliced = schedule.slice(0));
while (now > sliced[0].end){
sliced.shift();
}
return sliced[0];
}).share();
let cleanup$ = event$.filter((evt)=>evt.action === 'reloadSchedule')
let userNotification$ = event$.map(()=>{
notificationsCleared = false;
someAsyncNotification()
.then(()=>notificationsCleared = true)
});
let userSub = userNotification.subscribe(()=>{});
let cleanupSub = cleanup$.subscribe(()=>{
loadSchedule();
userSub.unsubscribe();
cleanupSub.unsubscribe();
});
}
};

Should I create a .factory in order to share a variable's value to two ajax requests?

Using angularjs I have made two ajax json requests, the first request is retrieving channel's online / offline status and the second ajax json request is for the channel's info (logo, name, status etc).
I assigned the variable signal to 'data.stream' which is the online / offline status for channels to share signal between json requests. In Google Developer console I am receiving a value of null. I did some research here http://www.ng-newsletter.com/posts/beginner2expert-services.html and found that using a service might be a solution. I followed the directions but I'm still unable to get the scope between json request to recognize signal.
I read that rootscope could be used but it's not recommend, not sure how true that is because a novice using angular but I want start my angular journey by applying best practices.
Recap: Using Angular Ajax to make a jsonp request to twitch api, I am make two requests one to retrieve the online / offline status of channels in my streamers array, and the other ajax json request is to retrieve the channel's info, and I need the scope of the variable signal which has been assigned the value data.stream to be seen between ajax jsonp requests. Please let me know if this is a logical approach or if I'm "going around the world again".
Here is a plunker: plnkr.co/edit/6sb39NktX7CwfnQFxNNX
// creating a service for signal ?
app.factory('signal', function() {
var signal = {};
return signal;
})
// declaring my TwitchController and dependencies
app.controller('TwitchController', ['$scope', '$http', 'signal', function($scope, $http, signal) {
// streamers array with array of channels
var streamers = ["freecodecamp", "storbeck", "terakilobyte", "habathcx", "RobotCaleb", "thomasballinger", "noobs2ninjas", "beohoff", "medrybw"];
$scope.imgs;
$scope.signal = signal;
var self = this;
//empty array
self.info = [];
for (var i = 0; i < streamers.length; i++) {
var url = "https://api.twitch.tv/kraken/channels/";
var streamUrl = "https://api.twitch.tv/kraken/streams/";
var callback = "/?callback=JSON_CALLBACK";
//json ajax request for channels online and offline status
$http.jsonp(streamUrl + streamers[i] + callback).success(function(data) {
//provides status of shows online and offline
signal = data.stream;
console.log(signal)
});
// json ajax request for channels and channel's info
$http.jsonp(url + streamers[i] + callback).success(function(data) {
// if channel does not have a logo image, this jpg will be the placeholder
// if statement test channel status (online or offline)
if (!signal) {
signal = "offline";
} else if (signal) {
signal = 'online';
}
// pushing retreive data from twitch.tv into array self.info
self.info.push(data);
});
Your issue here is async call to $http. You need to use $http.then and chain the promises. You do not need a factory for this instance but it is best practice to have one that just returns your info object. I didn't know exactly the format you wanted so I created one. Here is the plunker: http://plnkr.co/edit/ecwk0vGMJCvkqCbZa7Cw?p=preview
var app = angular.module('Twitch', []);
// declaring my TwitchController and dependencies
app.controller('TwitchController', ['$scope', '$http', function($scope, $http) {
// streamers array with array of channels
var streamers = ['freecodecamp', 'storbeck','terakilobyte', 'habathcx', 'RobotCaleb', 'thomasballinger', 'noobs2ninjas', 'beohoff', 'medrybw' ];
//empty array
$scope.info = [];
var url = 'https://api.twitch.tv/kraken/channels/';
var streamUrl = 'https://api.twitch.tv/kraken/streams/';
var callback = '/?callback=JSON_CALLBACK';
angular.forEach(streamers, function(stream) {
//json ajax request for channels online and offline status
$http.jsonp(streamUrl + stream + callback).then(function (data) {
//provides status of shows online and offline
// json ajax request for channels and channel's info
$http.jsonp(url + stream + callback).then(function (channelData) {
// pushing retrieve data from twitch.tv into array self.info
$scope.info.push(
{
url: url + stream,
stream: stream,
status: !data.stream ? 'offline' : 'online', // ternary statement test channel status (online or offline)
logo: channelData.data.logo ? channelData.data.logo : 'placeholderlogo.jpg' // if channel does not have a logo image, this jpg will be the placeholder
}
);
});
});
});
}]);

Resources