I would expect the following two pieces of code to be equivalent. Repeat uses a currentThread scheduler by default. If we change it ti an immediate scheduler:
Rx.Observable.fromArray([1,2,3,4,5]).flatMap(a => {
return Rx.Observable.repeat(a, 3, Rx.Scheduler.immediate)
})
.subscribe(r => console.log(r));
The code above produces the expected result: 1, 1, 1, 2, 2, 2, .... But the following code doesn't, and produces a sequence of mixed values:
Rx.Observable.fromArray([1,2,3,4,5]).flatMap(a => {
return Rx.Observable.repeat(a, 3).observeOn(Rx.Scheduler.immediate)
})
.subscribe(r => console.log(r));
I don't understand this behavior, but I guess I am missing something. repeat can be passed a Scheduler parameter, but I thought I could also force the Observable on a particular Scheduler by using observeOn. What am I missing?
The difference is that one is using the scheduler for generation, the second is just using it for propagation.
In the second version you are still using currentThread for the creation of the values. observeOn will only coerce values onto different schedulers after they are emitted from the previous operator but for operators that generate events this won't affect the generation of those events.
If you look inside of some of the creation operators (like fromArray) you'll see something like:
//Changing the scheduler will change how recursive scheduling works
scheduler.schedulerRecursiveWithState(0, function(self, state) {
if (i < len) {
observer.onNext(array[i]);
//Schedule the next event
self(i + 1);
} else {
observer.onCompleted();
}
});
whereas observeOn is akin to doing something like:
//Doesn't change when events get generated, simply reschedules them for down stream
source.subscribe(function(x) {
scheduler.scheduleWithState(x, function(self, state) {
observer.onNext(x);
});
});
Related
How is it possible with RXJS to make a cascaded forEach loop? Currently, I have 4 observables containing simple string lists, called x1 - x4. What I want to achieve now is to run over all variation and to call a REST-Api with an object of variation data. Usually, I would do something like that with a forEach, but how to do with RXJS? Please see the abstracted code:
let x1$ = of([1,2]);
let x2$ = of([a,b,c,d,e,f]);
let x3$ = of([A,B,C,D,E,F]);
let x4$ = of([M,N,O,P]);
x1$.forEach(x1 => {
x2$.forEach(x2 => {
x3$.forEach(x3 => {
x4$.forEach(x4 => {
let data = {
a: x1,
b: x2,
c: x3,
d: x4
}
return this.restService.post('/xxxx', data)
})
})
})
})
Is something like that possible with RXJS in an elegant way?
Let's assume you have a function combineLists which represent the plain-array version of the logic to turn static lists into an array of request observables:
function combineLists(lists: unknown[][]) {
const [x1s, x2s, x3s, x4s] = lists;
// Calculate combinations, you can also use your forEach instead
const combinations = x1s
.flatMap(a => x2s
.flatMap(b => x3s
.flatMap(c => x4s
.flatMap(d => ({a, b, c, d})))));
return combinations.map(combination => this.restService.post('/xxxx', combination));
}
Since your input observables are one-offs as well, we can use e.g. forkJoin. This waits for all of them to complete and then runs with their respective plain values. At this point you're back to computing the combinations with your preferred method.
forkJoin([x1$, x2$, x3$, x4$]).pipe(
map(combineLists),
);
Assuming your REST call is typed to return T, the above produces Observable<Observable<T>[]>. How you proceed from here depends on what data structure you're looking for / how you want to continue working with this. This didn't seem to be part of your question anymore, but I'll give a couple hints nonetheless:
If you want a Observable<T>, you can just add e.g. a mergeAll() operator. This observable will just emit the results of all individual requests after another in whichever order they arrive.
forkJoin([x1$, x2$, x3$, x4$]).pipe(
map(combineLists),
mergeAll(),
);
If you want an Observable<T[]> instead, which collects the results into a single emission, you could once again forkJoin the produced array of requests. This also preserves the order.
forkJoin([x1$, x2$, x3$, x4$]).pipe(
map(combineLists),
switchMap(forkJoin),
);
Some words of caution:
Don't forget to subscribe to make it actually do something.
You should make sure to handle errors on all your REST calls. This must happen right at the call itself, not after this entire pipeline, unless you want one single failed request to break the entire pipe.
Keep in mind that forkJoin([]) over an empty array doesn't emit anything.
Triggering a lot of requests like this probably means the API should be changed (if possible) as the number of requests grows exponentially.
In the application combineLatest is used to combine three observables:
class SomeComponent {
private heightProvider = new SubjectProvider<any>(this);
private marginsProvider = new SubjectProvider<any>(this);
private domainProvider = new SubjectProvider<any>(this);
arbitraryMethod(): void {
combineLatest([
this.heightProvider.value$,
this.marginsProvider.value$,
this.domainProvider.value$
]).pipe(
map(([height, margins, domain]) => {
// ...
}
}
setHeight(height: number): void {
this.heightProvider.next(height);
}
setMargins(margins: {}): void {
this.marginsProvider.next(margins);
}
setDomain(domain: []): void {
this.domainProvider.next(domain);
}
}
However, I've noticed a few times already that I am sometimes forgetting to set one of these observables.
Is there a way I can build in error handeling that throws to console once one of these isn't set?
Observables aren't typically 'set' or 'not set'. I'm not sure what you mean by this. If you have a predicate that can check your observables, here is how you might use it.
// predicate
function notSet(o: Observable<any>): Boolean{
//...
}
scale$: Observable<any> = defer(() => {
const combining = [
this.heightProvider.value$,
this.marginsProvider.value$,
this.domainProvider.value$
];
const allSet = !combining.find(notSet)
if(!allSet) console.log("Not Set Error");
return !allSet?
EMPTY :
combineLatest(combining).pipe(
map(([height, margins, domain]) => {
// ...
}
Update
Ensursing source observables have emitted
If I understand your problem properly, you want to throw an error if any of your source observables haven't emitted yet. At its heart, this feels like a simple problem, but it happens to be a problem for which there doesn't exist a single general solution.
Your solution has to be domain-specific to some extent.
A simplified example of a similar problem
What you're asking a similar to this:
How do I throw an error if 'add' isn't invoked with a second number?
const add = (a: number) => (b: number): number => {
// How do I throw an error if this function
// isn't invoked with a second number?
return a + b;
}
/***********
* Example 1
***********/
// add is being called with one number
const add5 = add(5);
...
/* More code here */
...
// add is being called with a second number
const result = add5(50);
console.log(result); // Prints "55"
/***********
* Example 2
***********/
const result = add(5)(20); // Add is being called with both numbers
console.log(result); // Prints "55"
/***********
* Example 3
***********/
// add is being called with one number
const add5 = add(5);
...
/* More code here */
...
// add was never given a second number
return
// Add throws an error? How?
How can you write add such that it throws an error if the second number isn't 'set'? Well, there's no simple answer. add doesn't know the future and can't guess whether that second number was forgotten or will still be set in the future. To add, those two scenarios look the same.
One solution is to re-write add so that it must take both parameters at once. If either is missing, throw an error:
const add = (a: number, b: number): number => {
if(a != null && b != null){
return a + b;
}
throw "add: invalid argument error";
}
This solution fundamentally changes how add works. This solution doesn't work if I have a requirement that add must take its arguments one at a time.
If I want add to keep that behaviour, perhaps I can set a timer and throw an error if the second argument isn't given fast enough.
const add = (a: number) => {
const t = setTimeout(
() => throw "add: argument timeout error"),
1000 // wait 1 second
);
return (b: number): number => {
clearTimeout(t); // cancel the error
return a + b;
}
}
Now add takes its arguments one at a time, but is a timeout really how I want this to work? Maybe I only care that add is given a second parameter before some other event (an API call returns or a user navigates away from the page) or something.
Hopefully, you can begin to understand how such a "simple" problem has only domain-specific solutions.
Observables
Your question, as writ, doesn't tell us enough about what you're trying to accomplish to guess what behaviour you want.
Observables have a lot of power built into them to allow you to design a solution specific to your needs. It's almost certain that you can throw an error if one of your observables isn't set, but first, you must define what this even means.
Is it not set quickly enough? Is it not set in time for a certain function call? Not set when an event is raised? Never set? How would you like to define never? When the program is shut down?
Maybe you could switch your Subjects for BehaviourSubjects so that they MUST always have a value set (sort of like add taking both arguments at once instead of one at a time).
All of these things (and many many many more) are possible.
I have tried to unsubscribe within the subscribe method. It seems like it works, I haven't found an example on the internet that you can do it this way.
I know that there are many other possibilities to unsubscribe the method or to limit it with pipes. Please do not suggest any other solution, but answer why you shouldn't do that or is it a possible way ?
example:
let localSubscription = someObservable.subscribe(result => {
this.result = result;
if (localSubscription && someStatement) {
localSubscription.unsubscribe();
}
});
The problem
Sometimes the pattern you used above will work and sometimes it won't. Here are two examples, you can try to run them yourself. One will throw an error and the other will not.
const subscription = of(1,2,3,4,5).pipe(
tap(console.log)
).subscribe(v => {
if(v === 4) subscription.unsubscribe();
});
The output:
1
2
3
4
Error: Cannot access 'subscription' before initialization
Something similar:
const subscription = of(1,2,3,4,5).pipe(
tap(console.log),
delay(0)
).subscribe(v => {
if (v === 4) subscription.unsubscribe();
});
The output:
1
2
3
4
This time you don't get an error, but you also unsubscribed before the 5 was emitted from the source observable of(1,2,3,4,5)
Hidden Constraints
If you're familiar with Schedulers in RxJS, you might immediately be able to spot the extra hidden information that allows one example to work while the other doesn't.
delay (Even a delay of 0 milliseconds) returns an Observable that uses an asynchronous scheduler. This means, in effect, that the current block of code will finish execution before the delayed observable has a chance to emit.
This guarantees that in a single-threaded environment (like the Javascript runtime found in browsers currently) your subscription has been initialized.
The Solutions
1. Keep a fragile codebase
One possible solution is to just ignore common wisdom and continue to use this pattern for unsubscribing. To do so, you and anyone on your team that might use your code for reference or might someday need to maintain your code must take on the extra cognitive load of remembering which observable use the correct scheduler.
Changing how an observable transforms data in one part of your application may cause unexpected errors in every part of the application that relies on this data being supplied by an asynchronous scheduler.
For example: code that runs fine when querying a server may break when synchronously returned a cashed result. What seems like an optimization, now wreaks havoc in your codebase. When this sort of error appears, the source can be rather difficult to track down.
Finally, if ever browsers (or you're running code in Node.js) start to support multi-threaded environments, your code will either have to make do without that enhancement or be re-written.
2. Making "unsubscribe inside subscription callback" a safe pattern
Idiomatic RxJS code tries to be schedular agnostic wherever possible.
Here is how you might use the pattern above without worrying about which scheduler an observable is using. This is effectively scheduler agnostic, though it likely complicates a rather simple task much more than it needs to.
const stream = publish()(of(1,2,3,4,5));
const subscription = stream.pipe(
tap(console.log)
).subscribe(x => {
if(x === 4) subscription.unsubscribe();
});
stream.connect();
This lets you use a "unsubscribe inside a subscription" pattern safely. This will always work regardless of the scheduler and would continue to work if (for example) you put your code in a multi-threaded environment (The delay example above may break, but this will not).
3. RxJS Operators
The best solutions will be those that use operators that handle subscription/unsubscription on your behalf. They require no extra cognitive load in the best circumstances and manage to contain/manage errors relatively well (less spooky action at a distance) in the more exotic circumstances.
Most higher-order operators do this (concat, merge, concatMap, switchMap, mergeMap, ect). Other operators like take, takeUntil, takeWhile, ect let you use a more declarative style to manage subscriptions.
Where possible, these are preferable as they're all less likely to cause strange errors or confusion within a team that is using them.
The examples above re-written:
of(1,2,3,4,5).pipe(
tap(console.log)
first(v => v === 4)
).subscribe();
It's working method, but RxJS mainly recommend use async pipe in Angular. That's the perfect solution. In your example you assign result to the object property and that's not a good practice.
If you use your variable in the template, then just use async pipe. If you don't, just make it observable in that way:
private readonly result$ = someObservable.pipe(/...get exactly what you need here.../)
And then you can use your result$ in cases when you need it: in other observable or template.
Also you can use pipe(take(1)) or pipe(first()) for unsubscribing. There are also some other pipe methods allowing you unsubscribe without additional code.
There are various ways of unsubscribing data:
Method 1: Unsubscribe after subscription; (Not preferred)
let localSubscription = someObservable.subscribe(result => {
this.result = result;
}).unsubscribe();
---------------------
Method 2: If you want only first one or 2 values, use take operator or first operator
a) let localSubscription =
someObservable.pipe(take(1)).subscribe(result => {
this.result = result;
});
b) let localSubscription =
someObservable.pipe(first()).subscribe(result => {
this.result = result;
});
---------------------
Method 3: Use Subscription and unsubscribe in your ngOnDestroy();
let localSubscription =
someObservable.subscribe(result => {
this.result = result;
});
ngOnDestroy() { this.localSubscription.unsubscribe() }
----------------------
Method 4: Use Subject and takeUntil Operator and destroy in ngOnDestroy
let destroySubject: Subject<any> = new Subject();
let localSubscription =
someObservable.pipe(takeUntil(this.destroySubject)).subscribe(result => {
this.result = result;
});
ngOnDestroy() {
this.destroySubject.next();
this.destroySubject.complete();
}
I would personally prefer method 4, because you can use the same destroy subject for multiple subscriptions if you have in a single page.
I want my observable to fire immediately, and again every second. interval will not fire immediately. I found this question which suggested using startWith, which DOES fire immediately, but I then get a duplicate first entry.
Rx.Observable.interval(1000).take(4).startWith(0).subscribe(onNext);
https://plnkr.co/edit/Cl5DQ7znJRDe0VTv0Ux5?p=preview
How can I make interval fire immediately, but not duplicate the first entry?
Before RxJs 6:
Observable.timer(0, 1000) will start immediately.
RxJs 6+
import {timer} from 'rxjs/observable/timer';
timer(0, 1000).subscribe(() => { ... });
RxJs 6. Note: With this solution, 0 value will be emitted twice (one time immediately by startWith, and one time by interval stream after the first "tick", so if you care about the value emitted, you could consider startWith(-1) instead of startWith(0)
interval(100).pipe(startWith(0)).subscribe(() => { //your code });
or with timer:
import {timer} from 'rxjs/observable/timer';
timer(0, 100).subscribe(() => {
});
With RxJava2, there's no issue with duplicated first entry and this code is working fine:
io.reactivex.Observable.interval(1, TimeUnit.SECONDS)
.startWith(0L)
.subscribe(aLong -> {
Log.d(TAG, "test"); // do whatever you want
});
Note you need to pass Long in startWith, so 0L.
RxJava 2
If you want to generate a sequence [0, N] with each value delayed by D seconds, use the following overload:
Observable<Long> interval(long initialDelay, long period, TimeUnit unit)
initialDelay - the initial delay time to wait before emitting the first value of 0L
Observable.interval(0, D, TimeUnit.SECONDS).take(N+1)
You can also try to use startWith(0L) but it will generate sequence like: {0, 0, 1, 2...}
I believe something like that will do the job too:
Observable.range(0, N).delayEach(D, TimeUnit.SECONDS)
I am wondering if there's a way to create a promise chain that I can build based on a series of if statements and somehow trigger it at the end. For example:
// Get response from some call
callback = (response) {
var chain = Q(response.userData)
if (!response.connected) {
chain = chain.then(connectUser)
}
if (!response.exists) {
chain = chain.then(addUser)
}
// etc...
// Finally somehow trigger the chain
chain.trigger().then(successCallback, failCallback)
}
A promise represents an operation that has already started. You can't trigger() a promise chain, since the promise chain is already running.
While you can get around this by creating a deferred and then queuing around it and eventually resolving it later - this is not optimal. If you drop the .trigger from the last line though, I suspect your task will work as expected - the only difference is that it will queue the operations and start them rather than wait:
var q = Q();
if(false){
q = q.then(function(el){ return Q.delay(1000,"Hello");
} else {
q = q.then(function(el){ return Q.delay(1000,"Hi");
}
q.then(function(res){
console.log(res); // logs "Hi"
});
The key points here are:
A promise represents an already started operation.
You can append .then handlers to a promise even after it resolved and it will still execute predictably.
Good luck, and happy coding
As Benjamin says ...
... but you might also like to consider something slightly different. Try turning the code inside-out; build the then chain unconditionally and perform the tests inside the .then() callbacks.
function foo(response) {
return = Q().then(function() {
return (response.connected) ? null : connectUser(response.userData);
}).then(function() {
return (response.exists) ? null : addUser(response.userData);//assuming addUser() accepts response.userData
});
}
I think you will get away with returning nulls - if null doesn't work, then try Q() (in two places).
If my assumption about what is passed to addUser() is correct, then you don't need to worry about passing data down the chain - response remains available in the closure formed by the outer function. If this assumption is incorrect, then no worries - simply arrange for connectUser to return whatever is necessary and pick it up in the second .then.
I would regard this approach to be more elegant than conditional chain building, even though it is less efficient. That said, you are unlikely ever to notice the difference.