I have async function handleParamsChanges which can take a few seconds to resolve. And I'm calling it when observable emits value:
this._activatedRoute.params
.subscribe(params => {
this.handleParamsChanges(params).then(() => {
// new value can be processed now
});
});
How could I modify my code so that if observable emits 2 values one after another, first handleParamsChanges is called for first value, and only after this promise resolves, it's called with second value, and so on.
Edit:
Here is the solution I came up with, but I'm guessing there is a better way to do this:
const params$ = this._activatedRoute.params;
const canExecute$ = new BehaviorSubject(true);
combineLatest(params$, canExecute$)
.pipe(
filter(([_, canExecute]) => canExecute),
map(([params]) => params),
distinctUntilChanged()
)
.subscribe(async params => {
canExecute$.next(false);
try {
await this.handleParamsChanges(params);
} catch (e) {
console.log(e);
} finally {
canExecute$.next(true);
}
})
I'm using canExecute$ to delay processing of new value.
I need to use distinctUntilChanged here to avoid creating infinite loop.
What you're looking for is concatMap. It waits for the previous "inner observable" to complete before subscribing again. Also you can greatly simplify your pipe:
params$.pipe(
concatMap(params => this.handleParamsChanges(params)),
).subscribe()
I'm trying to use takeUntil to stop a stream once a checkbox is switched off. I'm in Angular so for now I'm just concatenating a sting on a class property, not worrying about concatenating results of observables yet.
this.checked$ is a BehaviorSubject that starts false and gets nexted when my checkbox is de/activated. That part is for sure working at least somewhat because below will start to display the dots. However, adding the (commented out below) takeUntil(this.checked$) results in no dots displaying at all.
const source$ = interval(250).pipe(mapTo("."));
this.checked$
.pipe(
filter(c => {
return c === true;
}),
switchMap(() => {
console.log("switchMap");
// return source$.pipe(takeUntil(this.checked$));
return source$;
})
)
.subscribe(data => {
console.log("in subscribe", data, this.dots);
this.dots += data;
});
What is incorrect about how I am using BehaviorSubject and takeUntil here?
Here is a full example is on StackBlitz.
Note that takeUntil emits values only until the first checked$ is emited, here are good examples. Because checked$ is a BehaviorSubject it emits the current value immediately when you subscribe to it, as a result takeUntil stops.
A solution might be to switch between source$ and an empty observable inside the switchMap, like:
const source$ = interval(250).pipe(mapTo("."));
this.checked$
.pipe(
switchMap(c => {
console.log("switchMap");
return c ? source$ : EMPTY;
})
)
.subscribe(data => {
console.log("in subscribe", data, this.dots);
this.dots += data;
});
StackBlitz example
You can also fix your original solution by skipping the first value of checked$ subject, but I recommend you the previous solution because it's simpler.
const source$ = interval(250).pipe(mapTo("."));
this.checked$
.pipe(
filter(c => {
return c === true;
}),
switchMap(() => {
console.log("switchMap");
// return source$.pipe(takeUntil(this.checked$));
return source$.pipe(takeUntil(this.checked$.pipe(skip(1))));
})
)
.subscribe(data => {
console.log("in subscribe", data, this.dots);
this.dots += data;
});
StackBlitz example
Another solution is to use Subject instead of BehaviorSubject. The difference is that the Subject doesn't hold current value and emits nothing when you subscribe to it. It emits only when next() is called. If you replace BehaviorSubject with Subject in your original solution, it will work as expected.
StackBlitz example
I'm new to RxJs and having trouble to achieve this in "RxJs way":
An infinite stream a$ emits a value a once a while.
async() takes the a and performs an async operation.
If a$ emits values while async is pending, only keep the latest one al.
After the previous async completes, if there is an al, run async(al).
And so on.
a$:----a1----------a2----a3-----------------------a4-----------
async(a1):------------end async(a4):---
async(a3):-----end
Here is what I came up with, a bit nasty:
var asyncIdle$ = new Rx.BehaviorSubject()
var asyncRunning$ = new Rx.Subject()
var async$ = asyncIdle$
function async (val) {
async$ = asyncRunning$
// do something with val
console.log(val + ' handling')
setTimeout(() => {
console.log(val + ' complete')
async$.next()
async$ = asyncIdle$
}, 2000)
}
// simulate a$
var a$ = Rx.Observable.fromEvent(document, 'click')
.mapTo(1)
.scan((acc, curr) => acc + curr)
.do(val => console.log('got ' + val))
a$.debounce(() => async$)
.subscribe(val => {
async(val)
})
I came up with this solution in typescript:
I have a simple Gate class that can be open or closed:
enum GateStatus {
open = "open",
closed = "closed"
}
class Gate {
private readonly gate$: BehaviorSubject<GateStatus>;
readonly open$: Observable<GateStatus>;
readonly closed$: Observable<GateStatus>;
constructor(initialState = GateStatus.open) {
this.gate$ = new BehaviorSubject<GateStatus>(initialState);
this.open$ = this.gate$
.asObservable()
.pipe(filter(status => status === GateStatus.open));
this.closed$ = this.gate$
.asObservable()
.pipe(filter(status => status === GateStatus.closed));
}
open() {
this.gate$.next(GateStatus.open);
}
close() {
this.gate$.next(GateStatus.closed);
}
}
The operator function is quite simple. At the beginning the gate is open. Before we start a request, we close it and when the request has finished we open it again.
The audit() will only let the most recent request-data pass when the gate is open.
export const requestThrottle = <T>(
requestHandlerFactory: (requestData: T) => Observable<any>
) => (requestData: Observable<T>) => {
const gate = new Gate();
return requestData.pipe(
audit(_ => gate.open$),
// NOTE: when the order is important, use concatMap() instead of mergeMap()
mergeMap(value => {
gate.close();
return requestHandlerFactory(value).pipe(finalize(() => gate.open()));
})
);
};
use it like this:
src.pipe(
requestThrottle(() => of(1).pipe(delay(100)))
);
code exmaple on stackblitz
You can use the audit operator to solve the problem, like this (the comments should explain how it works):
// Simulate the source.
const source = Rx.Observable.merge(
Rx.Observable.of(1).delay(0),
Rx.Observable.of(2).delay(10),
Rx.Observable.of(3).delay(20),
Rx.Observable.of(4).delay(150),
Rx.Observable.of(5).delay(300)
).do(value => console.log("source", value));
// Simulate the async task.
function asyncTask(value) {
return Rx.Observable
.of(value)
.do(value => console.log(" before async", value))
.delay(100)
.do(value => console.log(" after async", value));
}
// Compose an observable that's based on the source.
// Use audit to ensure a value is not emitted until
// the async task has been performed.
// Use share so that the signal does not effect a
// second subscription to the source.
let signal;
const audited = source
.audit(() => signal)
.mergeMap(value => asyncTask(value))
.share();
// Compose a signal from the audited observable to
// which the async task is applied.
// Use startWith so that the first emitted value
// passes the audit.
signal = audited
.mapTo(true)
.startWith(true);
audited.subscribe(value => console.log("output", value));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs#5/bundles/Rx.min.js"></script>
A Solution With RxJS v6+ Primatives
Here's a solution that works in current RxJS versions (now available as the Node package rxjs-audit-with):
export function auditWith<T, R>(
callback: (value: T) => Promise<any>,
): OperatorFunction<T, R> {
const freeToRun = new BehaviorSubject(true);
return (source: Observable<T>) => {
return source.pipe(
audit(_val => freeToRun.pipe(filter(free => free))),
tap(() => freeToRun.next(false)),
mergeMap(val => callback(val)),
tap(() => freeToRun.next(true)),
);
};
}
How It Works
auditWith is an operator that uses a BehaviorSubject to track whether callback is busy. It uses audit to keep only the most recent value to come down the pipe, and releases that value the next time the BehaviorSubject is marked as free. tap goes before and after the async call in order to make it as busy/free (respectively), and mergeMap is used to actually call the async callback. Note that callback receives the latest value from the pipe, but can return anything it wants (if anything at all); the same value will propagate down the pipe unchanged (by design).
An Example of Use
async function slowFunc(num: number) {
await new Promise(resolve =>
setTimeout(() => {
console.log("Processed num", num);
resolve(true);
}, 3000),
);
}
interval(500)
.pipe(
take(13),
auditWith(slowFunc),
)
.subscribe();
// Sample output:
// Processed num 0
// Processed num 5
// Processed num 11
// Processed num 12
Even though sequential numbers are generated by interval every 500ms, auditWith will hold the latest value in the pipe until slowFunc has returned from its previous iteration. Note that the very first value is guaranteed to be run (because the async function is assumed to start in the "available" state), and the last value in the Observable is also guaranteed to run (i.e. the pipe will drain).
use a combination of first() and repeat(). if a$ finished emission the sequence complete
//emit every 1s
const a$=new Rx.BehaviorSubject(0)
Rx.Observable.interval(1000).take(100).skip(1).subscribe(a$);
// //simulate aysnc
const async = (val)=>{
console.log('async start with:'+ val)
return Rx.Observable.timer(5100).mapTo('async done:'+val);
}
a$.first().switchMap(value=>async(value))
.repeat()
.catch(e=>Rx.Observable.empty())
.subscribe(console.log,console.err,console.warn)
a$.subscribe(console.warn)
https://jsbin.com/tohahod/65/edit?js,console
Here:
import Rx from 'rxjs';
function fakeApi(name, delay, response) {
return new Rx.Observable(observer => {
console.log(`${name}: Request.`)
let running = true;
const id = setTimeout(() => {
console.log(`${name}: Response.`)
running = false;
observer.next(response);
observer.complete();
}, delay);
return () => {
if(running) console.log(`${name}: Cancel.`)
clearTimeout(id);
}
})
}
function apiSearch() { return fakeApi('Search', 4000, "This is a result of the search."); }
//============================================================
const messages$ = new Rx.Subject();
const toggle$ = messages$.filter(m => m === 'toggle');
const searchDone$ = toggle$.flatMap(() =>
apiSearch().takeUntil(toggle$)
);
searchDone$.subscribe(m => console.log('Subscriber:', m))
setTimeout(() => {
// This one starts the API call.
toggle$.next('toggle');
}, 2000)
setTimeout(() => {
// This one should only cancel the API call in progress, not to start a new one.
toggle$.next('toggle');
}, 3000)
setTimeout(() => {
// And this should start a new request again...
toggle$.next('toggle');
}, 9000)
my intent is to start the API call and stop it when it is in progress by the same toggle$ signal. Problem with the code is that toggle$ starts a new API call every time. I would like it not to start the new call when there is one already running, just to stop the one which is already in progress. Some way should I "unsubscribe" the outermost flatMap from toggle$ stream while apiSearch() is running. I guess that there is a need to restructure the code to achieve the behaviour... What is the RxJS way of doing that?
UPDATE: After some more investigations and user guide lookups, I came with this:
const searchDone$ = toggle$.take(1).flatMap(() =>
apiSearch().takeUntil(toggle$)
).repeat()
Works like it should. Still feels cryptic a little bit. Is this how you RxJS guys would solve it?
I think your solution will work only once since you're using take(1). You could do it like this:
const searchDone$ = toggle$
.let(observable => {
let pending;
return observable
.switchMap(() => {
let innerObs;
if (pending) {
innerObs = Observable.empty();
} else {
pending = innerObs = apiSearch();
}
return innerObs.finally(() => pending = null);
});
});
I'm using let() only to wrap pending without declaring it in parent scope. The switchMap() operator unsubscribes for you automatically without using take*().
The output with your test setTimeouts will be as follows:
Search: Request.
Search: Cancel.
Search: Request.
Search: Response.
Subscriber: This is a result of the search.
I've got 2 nested Observable Streams which do HTTP requests. Now I'd like to display a loading indicator, but can't get it working correctly.
var pageStream = Rx.createObservableFunction(_self, 'nextPage')
.startWith(1)
.do(function(pageNumber) {
pendingRequests++;
})
.concatMap(function(pageNumber) {
return MyHTTPService.getPage(pageNumber);
})
.do(function(response) {
pendingRequests--;
});
Rx.createObservableFunction(_self, 'search')
.flatMapLatest(function(e) {
return pageStream;
})
.subscribe();
search();
nextPage(2);
nextPage(3);
search();
This will trigger pendingRequests++ 4 times, but pendingRequests-- only once, because flatMapLatest will cancel the inner observable before the first 3 HTTP responses arrive.
I couldn't find anything like an onCancel callback. I also tried onCompleted and onError, but those too won't get triggered by flatMapLatest.
Is there any other way to get this working?
Thank you!
EDIT: Desired loading indicator behavior
Example: Single search() call.
search() -> start loading indicator
when search() response comes back -> disable loading indicator
Example: search() and nextPage() call. (nextPage() is called before search() response came back.)
search() -> start loading indicator
nextPage() -> indicator is already started, though nothing to do here
stop loading indicator after both responses arrived
Example: search(), search(). (search() calls override each other, though the response of the first one can be dismissed)
search() -> start loading indicator
search() -> indicator is already started, though nothing to do here
stop loading indicator when the response for the second search() arrived
Example: search(), nextPage(), search(). (Again: Because of the second search(), the responses from the previous search() and nextPage() can be ignored)
search() -> start loading indicator
nextPage() -> indicator is already started, though nothing to do here
search() -> indicator is already started, though nothing to do here
stop loading indicator when response for the second search() arrived
Example: search(), nextPage(). But this time nextPage() is called after search() response came back.
search() -> start loading indicator
stop loading indicator because search() response arrived
nextPage() -> start loading indicator
stop loading indicator because nextPage() response arrived
I tried using pendingRequests counter, because I can have multiple relevant requests at the same time (for example: search(), nextPage(), nextPage()). Then of course I'd like to disable the loading indicator after all those relevant requests finished.
When calling search(), search(), the first search() is irrelevant. Same applies for search(), nextPage(), search(). In both cases there's only one active relevant request (the last search()).
With switchMap aka flatMapLatest you want to trim asap execution of the current inner-stream as new outer-items arrive. It is surely a good design decision as otherwise it would bring a lot of confusion and allow some spooky actions. If you really wanted do do something onCancel you can always create your own observable with custom unsubscribe callback. But still I would recommend not to couple unsubscribe with changing state of the external context. Ideally the unsubscribe would only clean up internally used resources.
Nevertheless your particular case can be solved without accessing onCancel or similar. The key observation is - if I understood your use case correctly - that on search all previous / pending actions may be ignored. So instead of worry about decrementing the counter we can simply start counting from 1.
Some remarks about the snippet:
used BehaviorSubject for counting pending requests - as it is ready to be composed with other streams;
checked all cases you posted in your question and they work;
added some fuzzy tests to demonstrate correctness;
not sure if you wanted to allow nextPage when a search is still pending - but it seems to be just a matter of using concatMapTo vs merge;
used only standard Rx operators.
PLNKR
console.clear();
const searchSub = new Rx.Subject(); // trigger search
const nextPageSub = new Rx.Subject(); // triger nextPage
const pendingSub = new Rx.BehaviorSubject(); // counts number of pending requests
const randDurationFactory = min => max => () => Math.random() * (max - min) + min;
const randDuration = randDurationFactory(250)(750);
const addToPending = n => () => pendingSub.next(pendingSub.value + n);
const inc = addToPending(1);
const dec = addToPending(-1);
const fakeSearch = (x) => Rx.Observable.of(x)
.do(() => console.log(`SEARCH-START: ${x}`))
.flatMap(() =>
Rx.Observable.timer(randDuration())
.do(() => console.log(`SEARCH-SUCCESS: ${x}`)))
const fakeNextPage = (x) => Rx.Observable.of(x)
.do(() => console.log(`NEXT-PAGE-START: ${x}`))
.flatMap(() =>
Rx.Observable.timer(randDuration())
.do(() => console.log(`NEXT-PAGE-SUCCESS: ${x}`)))
// subscribes
searchSub
.do(() => console.warn('NEW_SEARCH'))
.do(() => pendingSub.next(1)) // new search -- ingore current state
.switchMap(
(x) => fakeSearch(x)
.do(dec) // search ended
.concatMapTo(nextPageSub // if you wanted to block nextPage when search still pending
// .merge(nextPageSub // if you wanted to allow nextPage when search still pending
.do(inc) // nexpage started
.flatMap(fakeNextPage) // optionally switchMap
.do(dec) // nextpage ended
)
).subscribe();
pendingSub
.filter(x => x !== undefined) // behavior-value initially not defined
.subscribe(n => console.log('PENDING-REQUESTS', n))
// TEST
const test = () => {
searchSub.next('s1');
nextPageSub.next('p1');
nextPageSub.next('p2');
setTimeout(() => searchSub.next('s2'), 200)
}
// test();
// FUZZY-TEST
const COUNTER_MAX = 50;
const randInterval = randDurationFactory(10)(350);
let counter = 0;
const fuzzyTest = () => {
if (counter % 10 === 0) {
searchSub.next('s' + counter++)
}
nextPageSub.next('p' + counter++);
if (counter < COUNTER_MAX) setTimeout(fuzzyTest, randInterval());
}
fuzzyTest()
<script src="https://npmcdn.com/rxjs#5.0.0-beta.11/bundles/Rx.umd.js"></script>
One way: use the finally operator (rxjs4 docs, rxjs5 source). Finally triggers whenever the observable is unsubscribed or completes for any reason.
I'd also move the counter logic to inside the concatMap function since you are really counting the getPage requests, not the number of values that have gone through. Its a subtle difference.
var pageStream = Rx.createObservableFunction(_self, 'nextPage')
.startWith(1)
.concatMap(function(pageNumber) {
++pendingRequests;
// assumes getPage returns an Observable and not a Promise
return MyHTTPService.getPage(pageNumber)
.finally(function () { --pendingRequests; })
});
I wrote a solution for your problem from scratch.
For sure it might be written in a more functional way but it works anyway.
This solution is based on reqStack which contains all requests (keeping the call order) where a request is an object with id, done and type properties.
When the request is done then requestEnd method is called.
There are two conditions and at least one of them is enough to hide a loader.
When the last request on the stack was a search request then we can hide a loader.
Otherwise, all other requests have to be already done.
function getInstance() {
return {
loaderVisible: false,
reqStack: [],
requestStart: function (req){
console.log('%s%s req start', req.type, req.id)
if(_.filter(this.reqStack, r => r.done == false).length > 0 && !this.loaderVisible){
this.loaderVisible = true
console.log('loader visible')
}
},
requestEnd: function (req, body, delay){
console.log('%s%s req end (took %sms), body: %s', req.type, req.id, delay, body)
if(req === this.reqStack[this.reqStack.length-1] && req.type == 'search'){
this.hideLoader(req)
return true
} else if(_.filter(this.reqStack, r => r.done == true).length == this.reqStack.length && this.loaderVisible){
this.hideLoader(req)
return true
}
return false
},
hideLoader: function(req){
this.loaderVisible = false
console.log('loader hidden (after %s%s request)', req.type, req.id)
},
getPage: function (req, delay) {
this.requestStart(req)
return Rx.Observable
.fromPromise(Promise.resolve("<body>" + Math.random() + "</body>"))
.delay(delay)
},
search: function (id, delay){
var req = {id: id, done: false, type: 'search'}
this.reqStack.push(req)
return this.getPage(req, delay).map(body => {
_.find(this.reqStack, r => r.id == id && r.type == 'search').done = true
return this.requestEnd(req, body, delay)
})
},
nextPage: function (id, delay){
var req = {id: id, done: false, type: 'nextPage'}
this.reqStack.push(req)
return this.getPage(req, delay).map(body => {
_.find(this.reqStack, r => r.id == id && r.type == 'nextPage').done = true
return this.requestEnd(req, body, delay)
})
},
}
}
Unit tests in Moca:
describe('animation loader test:', function() {
var sut
beforeEach(function() {
sut = getInstance()
})
it('search', function (done) {
sut.search('1', 10).subscribe(expectDidHideLoader)
testDone(done)
})
it('search, nextPage', function (done) {
sut.search('1', 50).subscribe(expectDidHideLoader)
sut.nextPage('1', 20).subscribe(expectDidNOTHideLoader)
testDone(done)
})
it('search, nextPage, nextPage', function(done) {
sut.search('1', 50).subscribe(expectDidHideLoader)
sut.nextPage('1', 40).subscribe(expectDidNOTHideLoader)
sut.nextPage('2', 30).subscribe(expectDidNOTHideLoader)
testDone(done)
})
it('search, nextPage, nextPage - reverse', function(done) {
sut.search('1', 30).subscribe(expectDidNOTHideLoader)
sut.nextPage('1', 40).subscribe(expectDidNOTHideLoader)
sut.nextPage('2', 50).subscribe(expectDidHideLoader)
testDone(done)
})
it('search, search', function (done) {
sut.search('1', 60).subscribe(expectDidNOTHideLoader) //even if it takes more time than search2
sut.search('2', 50).subscribe(expectDidHideLoader)
testDone(done)
})
it('search, search - reverse', function (done) {
sut.search('1', 40).subscribe(expectDidNOTHideLoader)
sut.search('2', 50).subscribe(expectDidHideLoader)
testDone(done)
})
it('search, nextPage, search', function (done) {
sut.search('1', 40).subscribe(expectDidNOTHideLoader) //even if it takes more time than search2
sut.nextPage('1', 30).subscribe(expectDidNOTHideLoader) //even if it takes more time than search2
sut.search('2', 10).subscribe(expectDidHideLoader)
testDone(done)
})
it('search, nextPage (call after response from search)', function (done) {
sut.search('1', 10).subscribe(result => {
expectDidHideLoader(result)
sut.nextPage('1', 10).subscribe(expectDidHideLoader)
})
testDone(done)
})
function expectDidNOTHideLoader(result){
expect(result).to.be.false
}
function expectDidHideLoader(result){
expect(result).to.be.true
}
function testDone(done){
setTimeout(function(){
done()
}, 200)
}
})
Part of the output:
JSFiddle is here.
I think there's a much simpler solution, to explain it I'd like to "re-phrase" the examples you gave in your edit:
The status is "pending" as long as there are un-closed requests.
A response closes all previous requests.
Or, in stream/marbles fashion
(O = request [open], C = response [close], p = pending, x = not pending)
http stream: ------O---O---O---C---O---C---O---O---C---O---
------ Status: x----P--------------x---P----x----P---------x---P---
You can see that the count doesn't matter, we have a flag which is actually on (pending) or off (a response was returned). This true because of you switchMap/flatMap, or as you said at the end of your edit, at each time there's only one active request.
The flag is actually a boolean observable/oberver or simply a subject.
So, what you need is to first define:
var hasPending: Subject<boolean> = BehaviorSubject(false);
The BehaviorSubject is relevant for 2 reasons:
You can set an initial value (false = nothing pending).
New subscribers get the last value, hence, even components that are created later will know if you have a pending request.
Than the rest becomes simple, whenever you send out a request, set the pending to 'true', when a request is done, set the pending flag to 'false'.
var pageStream = Rx.createObservableFunction(_self, 'nextPage')
.startWith(1)
.do(function(pageNumber) {
hasPending.next(true);
})
.concatMap(function(pageNumber) {
return MyHTTPService.getPage(pageNumber);
})
.do(function(response) {
hasPending.next(false);
});
Rx.createObservableFunction(_self, 'search')
.flatMapLatest(function(e) {
return pageStream;
})
.subscribe();
This is rxjs 5 syntax, for rxjs 4 use onNext(...)
If you don't need your flat as an observable, just a value, just declare:
var hasPending: booolean = false;
Then in the .do before the http call do
hasPending = true;
and in the .do after the http call do
hasPending = false;
And that's it :-)
Btw, after re-reading everything, you can test this by an even simpler (though somewhat quick and dirty) solution:
Change your post http 'do' call to:
.do(function(response) {
pendingRequests = 0;
});