I'm currently using RxJS Observables / Subscription to perform HTTP requests as code similar to below demonstrates:
this.waiting = true;
this.doSomething().subscribe(
(result) => {
this.waiting = false;
this.showResult= true;
}
);
What I really want to do, is only set this.waiting to true on a predetermined length of time. In other words, you are only really 'waiting' if the Observable hasn't come back within say 30 seconds. Wondering how to achieve that. I see that there is a .timer method available, but that would only start subscribing after that length of time?
Have a look at timeout() and timeoutWith() operators. These have no documentation but from their parameters you can guess what they do.
The timeout() operator send an error notification after some time of inactivity.
The timeoutWith() I think let's you replace the source Observable with another Observable after some time of inactivity.
Eventually, if you want to avoid these two operators you can use Observable.race that subscribes internally only to the first Observable that emits:
Observable.race(Observable.timer(30 * 1000).take(1), this.doSomething())
I don't know an operator that does this but this js code should do
const source = Rx.Observable.of(1).delay(1000);
const sub = source.subscribe(val => console.log(val));
setTimeout(() => {
sub.unsubscribe();
console.log('timeout')
}, 500);
you can play around with delay and setTimeout values.
Related
Im trying to make throttleTime take effect, but for some reason it does not kick in. I have the following:
// Class Properties
private calendarPeriodSubject: Subject<x> = new Subject<x>();
private calendarPeriodObservable$ = this.calendarPeriodSubject.asObservable();
// Throttling fails here (Inside constructor):
const calendarPeriodSubscription = this.calendarPeriodObservable$.pipe(throttleTime(750)).subscribe(async (calendar: x) => {
// Do http stuff here
}
});
The subject gets called like this:
this.calendarPeriodSubject.next(x);
I also tried with:
this.calendarPeriodSubject.pipe(throttleTime(1000)).subscribe({next: (x) => x});
I would like to process the FIRST time, and the following clicks should not have any effect before after ieg 750ms - To prevent the server from getting spammed basically.
Anyone has any idea?
Thanks!
The problem is that you are using the wrong operator for your use case. The way I understand your explanation you want to send through your first call and stop any further calls to your Server for some amount of ms. But what throttleTime(sec) does is simply put a timer on the action and execute it sec ms later. So you server will still be spammed, just a few ms later.
Your case screams debounceTime() for me. debounceTime docu
This disables any further data to be passed though the Observable for the specified time after a value has been emitted.
Therefore your code should be fine if you use something like:
const calendarPeriodSubscription =
this.calendarPeriodObservable$.pipe(debounceTime(750)).subscribe((calendar: x) => {
// Stuff with returned data
});
I am trying to figure out the RxJS pattern for declaring my "subscribes" such that they execute in order.
Here's the situation I'm in:
var flag = false;
// Stream that non-deterministically emits `true` when flag is true
var random$ = Rx.Observable.interval(1000)
.map(() => setFlagToRandomTrueOrFalse())
.filter(x => x)
.first();
// How can I declare my "subscribes" so that the console.logs happen in order?
var sub1 = random$.subscribe(() => console.log(1), console.log, resetFlagToFalse);
var sub2 = random$.subscribe(() => console.log(2), console.log, resetFlagToFalse);
function setFlagToRandomTrueOrFalse() {
flag = flag || !!Math.floor(Math.random() * 2);
return flag;
}
function resetFlagToFalse() { flag = false; }
This currently prints 1 and 2 in random order due to the async nature of .subscribe.
Also, what is the proper name for these "subscribes"? Total RxJS noob here.
eeerr I am not sure the order you observe is because of the async nature of subscribe.
First of all, as currently written, random$ is a cold source, i.e. a producer which has not been subscribed yet. That producer will start producing every time you subscribe to it. That means here, you are starting the producer twice, so you will have two different random values emitted for each, so the log will be shown at different times, given that you have not subscribed to the same sequence of values, even when it looks very much like so.
A more clear description of the distinction between cold and hot streams is Hot and Cold observables : are there 'hot' and 'cold' operators?. Spend as much time as necessary to understand this because this is very important before you start to do anything fancy with Rxjs.
If you want to subscribe to the same sequence of values, i.e. if you want the producer to multicast its values to all subscribers (hot behavior), instead of restarting for each subscriber (cold behavior), you need to use a variation of the multicast operator, as explained in the abovementioned link. That means in your case
var random$ = Rx.Observable.interval(1000)
.map(() => setFlagToRandomTrueOrFalse())
.filter(x => x)
.share();
Going back to your question about data propagation order, it is indeed mostly deterministic, which does not mean always simple. In a simple case as yours, the first subscribe will indeed be executed first. That is because under the hood, multicast-like operators are using subjects, which emit immediately (hot source) the value they receive to all their subscribers, in order of subscription. For information about subjects, have a look at What are the semantics of different RxJS subjects?.
Last thing, it helps a lot to reason about your streams if you compose pure functions. Having a closure variable such as flag makes thing really more complex very fast.
I recommend you to have a look here before delving much further into Rxjs : The introduction to Reactive Programming you've been missing
You can use publish() and connect() to perform the actions in sequence like this
var flag = false;
// Stream that non-deterministically emits `true` when flag is true
var random$ = Rx.Observable.interval(1000)
.map(() => setFlagToRandomTrueOrFalse())
.filter(x => x)
.first().publish();
// How can I declare my "subscribes" so that the console.logs happen in order?
var sub1 = random$.subscribe(() => console.log(1), console.log, resetFlagToFalse);
var sub2 = random$.subscribe(() => console.log(2), console.log, resetFlagToFalse);
random$.connect();
function setFlagToRandomTrueOrFalse() {
flag = flag || !!Math.floor(Math.random() * 2);
return flag;
}
function resetFlagToFalse() { flag = false; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.js"></script>
My application (Angular 2 with RxJS 5 written in TypeScript) requires me to make 2 calls to a web service in a predefined order. Inspired by this page I decided to try the concat operator of RxJS observables. The twist is that the onNext callback that receives values needs to forward them to different handlers depending on where they came from
My code can be simplified to:
suscription1 = Observable.return({name:'John', age:7});
subscription2 = Observable.return({name:'Mary', color:'blue'});
Observable.concat(subscription1, subscription2).subscribe(
data=>{ /* need to know which subscripion
i'm dealing with to direct to the right handler*/}
);
What's the most elegant way to determine where my data came from at each observable emission? I have thought of a couple of ways but they all seem clumsy:
Keep a counter in scope, perhaps as a class property. Check it and increment it each time you receive a result. Since each observable emits just once, the counter will have its initial value when the first subscription outputs and it will have initial+1 when the second outputs. Problem is this won't work when I don't know how many times each observable will emit before being complete.
Inspect each result and identify its source by the result's shape. For instance, in my example only the first observable's result has an age property. Obviously this becomes impractical when the results have the same shape.
Get rid of the concat operator altogether; subscribe to subscription2 in the onComplete handler of subscription1. This would work, but it doesn't scale well; if I have 4 or 5 subscriptions to concatenate, it becomes nested hell.
Solution 3 with just 2 subscriptions...
suscription1 = Observable.return({name:'John', age:7});
subscription2 = Observable.return({name:'Mary', age:8});
subscription1.subscribe(
data=> this.handlerOne(data),
error=>{},
()=> {
subscription2.subscribe(
data=>this.handlerTwo(data),
error=>{},
()=>console.log('All subscriptions completed!')
)
}
);
So my question: When using Observable.concat to subscribe to several observables in sequence, how can my onNext handler determine where the data came from? Alternatively, is there another operator I should be using for this purpose? I cannot use the forkJoin operator because the order in which the subscriptions are completed is important.
You could mapthe suscriptions to know where it's coming from.
suscription1 = Observable.return({name:'John', age:7})
.map(person => { person.source = 1; return person };
subscription2 = Observable.return({name:'Mary', age:8})
.map(person => { person.source = 2; return person };
Then you can easily determine where it comes from:
Observable.concat(subscription1, subscription2).subscribe(
data=> { if (data.source == 1) /* do whatever */ }
);
Let's say that I want to pass a Scheduler to an RxJS operator that makes it emit notifications every 5 seconds. Of course, this is very easy to do by just using interval or other existing operators. But if I really want to use a scheduler to accomplish that, how would I go about it?
My first thought is to subclass Rx.Scheduler.default. Would that be the way to go? And if so, how could that subclass look? Again, I understand that this is a complicated way to accomplish something that's easy using operators, but I am just curious about custom schedulers.
Operations should always be independent of the Schedulers that are used to implement them. Schedulers only know about one thing, time. Every scheduler is specifically built to deal with its own notion of time. They are expressly not built to handle specific operators since that would be a conflation of concerns.
So for your stated goal of creating a recurring task, I wouldn't recommend trying to actually create your own scheduler, it simply isn't needed. Schedulers come with an interface that already supports this.
You can use either the schedulePeriodic or the scheduleRecursiveFuture to accomplish this.
//Using periodic
Rx.Observable.interval = function(period, scheduler) {
return Rx.Observable.create(function(observer) {
return scheduler.schedulePeriodic(0, period, function(count) {
observer.onNext(count);
return count + 1;
});
});
};
//Using scheduleRecursive
Rx.Observable.interval = function(period, scheduler) {
return Rx.Observable.create(function(observer) {
return scheduler.scheduleRecursiveFuture(0, period, function(count, self) {
observer.onNext(count);
self(period, count + 1);
});
});
};
Reference 1,
Reference 2;
The former should be easier to wrap your head around, essentially it is just scheduling something to occur repeatedly spaced in time based on the period parameter.
The latter is usually a little more difficult to explain, but essentially you are scheduling a task and then sometime during the execution of that task you are rescheduling it (which is what the self parameter) is doing. This allows you do get the same effect using the period parameter.
The timing of this work is all directly affected by which scheduler you decide to pass into the operator. For instance, if you pass in the default it will try to use the best method for an asynchronous completion, whether that be setTimeout, setInterval or some other thing I can't remember. If you pass in a TestScheduler or a HistoricalScheduler this actually won't do anything until you increment each of their respective clocks, but doing so gives fine grained control over how time flows.
tl;dr Only implement new Schedulers if you have some new overall notion of time to express, otherwise use the existing API to do work on whatever Scheduler best fits how you want time to pass.
Should you roll your own?
Plainly: No. Most likely you can get done what you need done with an existing operator. Something like buffer, window, sample, etc. Scheduler development is not completely straightforward.
How to roll your own RxJS 4 Scheduler
If you want to implement your own Scheduler, in RxJS 4, you'd subclass Rx.Scheduler, then override each schedule method: schedule, scheduleFuture, schedulePeriodic, scheduleRecursive, scheduleRecursiveFuture... You'd also likely want to override now to return something relevant to your schedule.
Here is an example of a custom scheduler that uses button clicks inside of real time
/**
NOTE: This is REALLY fast example. There is a lot that goes into implementing a
Scheduler in RxJS, for example what would `now()` do in the scheduler below? It's also missing a number of scheduling methods.
*/
class ButtonScheduler extends Rx.Scheduler {
/**
#param {string} the selector for the button (ex "#myButton")
*/
constructor(selector) {
super();
this.button = document.querySelector(selector);
}
schedule(state, action) {
const handler = (e) => {
action(state);
};
const button = this.button;
// next click the action will fire
button.addEventListener('click', handler);
return {
dispose() {
// ... unless you dispose of it
button.removeEventListener('click', handler);
}
};
}
// Observable.interval uses schedulePeriodic
schedulePeriodic(state, interval, action) {
const button = this.button;
let i = 0;
const handler = (e) => {
const count = i++;
if(count > 0 && count % interval === 0) {
state = action(state);
}
};
// next click the action will fire
button.addEventListener('click', handler);
return {
dispose() {
// ... unless you dispose of it
button.removeEventListener('click', handler);
}
};
}
}
Rx.Observable.interval(1, new ButtonScheduler('#go'))
.subscribe(x => {
const output = document.querySelector('#output');
output.innerText += x + '\n';
});
How to do it in RxJS 5 (alpha)
Scheduling changed again in RxJS 5, since that version was rewritten from the ground up.
In RxJS5, you can create any object that adheres to the following interface:
interface Scheduler {
now(): number
schedule(action: function, delay: number = 0, state?: any): Subscription
}
Where Subscription is just any object with an unsubscribe function (same as dispose, really)
Once again, though, I don't advise creating a scheduler unless it's completely necessary.
I really hope that helps answer your question.
I'm creating an interactive webpage with RxJs.
This is what I want to achieve:
I have an application that generates tokens. These tokens can be consumed by an external entity.
When a user creates a token, the page starts polling the webserver for its status (consumed or not). When the token is consumed, the page refreshes.
So, when the token is created, a request is sent to the server every 2 seconds asking whether the token is consumed yet.
I have an Observable of strings that represent my generatedTokens.
I actually already have a working implementation using the Rx.Scheduler.default class, which allows me to do things manually. However, I can't help but feel that there should be a much simpler, more elegant solution to this.
This is the current code:
class TokenStore {
constructor(tokenService, scheduler) {
// actual implementation omitted for clarity
this.generatedTokens = Rx.Observable.just(["token1", "token2"]);
this.consumedTokens = this.generatedTokens
.flatMap(token =>
Rx.Observable.create(function(observer) {
var notify = function() {
observer.onNext(token);
observer.onCompleted();
};
var poll = function() {
scheduler.scheduleWithRelative(2000, function() {
// tokenService.isTokenConsumed returns a promise that resolves with a boolean
tokenService.isTokenConsumed(token)
.then(isConsumed => isConsumed ? notify() : poll());
}
);
};
poll();
}));
}
}
Is there something like a "repeatUntil" method? I'm looking for an implementation that does the same thing as the code above, but looks more like this:
class TokenStore {
constructor(tokenService, scheduler) {
// actual implementation omitted for clarity
this.generatedTokens = Rx.Observable.just(["token1", "token2"]);
this.consumedTokens = this.generatedTokens
.flatMap(token =>
Rx.Observable.fromPromise(tokenService.isTokenConsumed(token))
.delay(2000, scheduler)
// is this possible?
.repeatUntil(isConsumed => isConsumed === true));
}
}
Funnily enough the answer struck me a few minutes after posting the question. I suppose rubberducking might not be so silly after all.
Anyway, the answer consisted of two parts:
repeatUntil can be achieved with a combination of repeat(), filter() and first()
fromPromise has some internal lazy cache mechanism which causes subsequent subscriptions to NOT fire a new AJAX request. Therefore I had to resort back to using Rx.Observable.create
The solution:
class TokenStore {
constructor(tokenService, scheduler) {
// actual implementation omitted for clarity
this.generatedTokens = Rx.Observable.just(["token1", "token2"]);
this.consumedTokens = this.generatedTokens
.flatMap(token =>
// must use defer otherwise it doesnt retrigger call upon subscription
Rx.Observable
.defer(() => tokenService.isTokenConsumed(token))
.delay(2000, scheduler)
.repeat()
.filter(isConsumed => isConsumed === true)
.first())
.share();
}
}
A minor sidenote: the "share()" ensures that both observables are hot, which avoids the scenario where every subscriber would cause ajax request to start firing.
class TokenSource {
constructor(tokenService, scheduler) {
this.generatedTokens = Rx.Observable.just(["token1", "token2"]).share();
this.consumedTokens = this.generatedTokens
.flatMap(token =>
Rx.Observable.interval(2000, scheduler)
.flatMap(Rx.Observable.defer(() =>
tokenService.isTokenConsumed(token)))
.first(isConsumed => isConsumed === true))
.share()
}
}
You can take advantage of two facts:
flatMap has an overload that takes an observable which will be resubscribed to every time a new event comes in
defer can take a method returning a promise. The method will be re-executed every subscription, which means you do not have to roll your own Promise->Observable conversion.