I want to emit one value when the original observable completes let's say like below, using the imaginary operator mapComplete:
let arr = ['a','b', 'c'];
from(arr)
.pipe(mapComplete(()=>'myValue'))
.pipe(map((v)=>`further processed: ${v}`))
.subscribe(console.log)
//further processed: myValue
I tried the following which work but don't seem suitable:
1.
from(arr)
.pipe(toArray())
.pipe(map(()=>'myValue'))
.pipe(map((v)=>`further processed: ${v}`))
.subscribe(console.log);
//further processed: myValue
Issue: If the original observable is a huge stream, i don't want to buffer it to an array, just to emit one value.
2.
from(arr)
.pipe(last())
.pipe(map(()=>'myValue'))
.pipe(map((v)=>`further processed: ${v}`))
.subscribe(console.log);
//further processed: myValue
issue: If the stream completes without emitting anything I get an error: [Error [EmptyError]: no elements in sequence]
What would be a correct (in rxjs terms) way to do the above?
You can achieve this with ignoreElements to not emit anything and endWith to emit a value on complete.
from(arr).pipe(
ignoreElements(),
endWith('myValue'),
map(v => `further processed: ${v}`)
).subscribe(console.log);
If you want to execute a function in map you could use count() beforehand to emit one value on complete (the amount of values emitted).
from(arr).pipe(
count(), // could also use "reduce(() => null, 0)" or "last(null, 0)" or "takeLast(1), defaultIfEmpty(0)"
map(() => getMyValue()),
map(v => `further processed: ${v}`)
).subscribe(console.log);
You may achieve what you want by building your own custom operator.
The code could look like this
const emitWhenComplete = <T>(val: T) => <U>(source: Observable<U>) =>
new Observable<T>((observer) => {
return source.subscribe({
error: (err) => observer.error(err),
complete: () => {
observer.next(val);
observer.complete();
},
});
});
Basically this operator would take in the source observable, ignore all values it emits, and emit only when the source completes.
You can look at this stackblitz for some tests.
You could also use the last() operator with a default value. It would remove the no elements in sequence error when the stream is empty.
from(arr).pipe(
last(null, 'myValue'), // `null` denotes no predicate
map(_ => 'myValue'), // map the last value from the stream
map((v)=>`further processed: ${v}`)
).subscribe(console.log);
Related
I have a stream of emissions conforming to: Observable<Notice[]>. Each Notice has a property, isVisible$ (Observable<boolean>) that determines whether or not it is on screen in this particular moment. I want to filter this array of notices by whether the most recent value of isVisible$ is true. When a new array of notices occurs, I want to begin the process again. I know this entails using switchMap on the higher order observable stream.
Neither types of observable will ever complete, so using operators like toArray() will not work here. Each isVisible$ stream is guaranteed to emit at least once.
I want the output to also be of Observable<Notice[]>, emitting each time the isVisible$ stream of any of the inner observable predicates updates.
What I have so far does emit the proper values, but the inner pipeline just groups notices together and emits them (via scan, in lieu of toArray), it doesn't buffer to the length of from(notices) and then emit (if that makes sense). This makes the end result of the stream is too busy.
notices.pipe(
switchMap(notices => from(notices).pipe(
mergeMap(notice => notice.isVisible$.pipe(
map(isVisible => ({ notice, isVisible }))
)),
filter(({ isVisible }) => isVisible),
map(({ notice }) => notice),
scan((noticesArr, noticeBeingAddedOrRemoved) => {
if (!noticesArr.find(n => n.identifier === noticeBeingAddedOrRemoved.id)) {
noticesArr.push(noticeBeingAddedOrRemoved);
}
return noticesArr;
}, [])
))
);
Here's a reproducible sample of what I'm working with on StackBlitz.
I've changed it to use zip, which will only emit when each of the isVisible$ observables emit. You could also use combineLatest if you want to emit whenever any of the source observables emit, rathern than waiting for all of them.
I am very new to RxJs and the problem I faced looks quite complicated for me. So, I have the following sample code
const requests = [...Array(10)].map((_, i) => fetch(`${ ENDPOINT }/${ ++i }`));
from(requests).pipe(
switchMap(response => response.json()),
catchError(val => of(val))
).subscribe(value => { ... })
I have an array of 10 requests which I would like to process somehow(as an array of resolved Promise values) in my subscribe handler. The example above works just fine when I pass in only one request instead of array, but when it comes to the array I receive
TypeError: response.json is not a function
You can use forkJoin. The important thing here is that in RxJS Promises are always turned into Observables that emit once and then complete. So you don't even need to make any conversion.
const requests = [
Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3),
];
forkJoin(requests).subscribe(console.log);
forkJoin is typically used with an array of Observables but it works with Promises as well with zero effort.
Live demo: https://stackblitz.com/edit/rxjs-gcorux
from accepts only 1 promise. You can solve it like this:
from(Promise.all(requests))
And use map instead of switchMap. In switchmap you should return another observable, not a value.
from(Promise.all(requests)).pipe(
map(responses => responses.map(response => response.json())),
catchError(val => of(val))
).subscribe(value => { ... })
And don't forget that processing an array of promises will return array of values, so you should not just get a response.json(), but do it for each element in the array
When from takes a promise as an argument it just converts it to an observable.
You can return an array of observables instead:
const requests = [...Array(10)].map((_, i) => from(fetch(`${ENDPOINT}/${++i}`)));
And then get their value by combining the streams, perhaps with forkJoin:
forkJoin(requests).subscribe(results => console.log(results));
I have a source stream that can emit two types of messages. I would like to separate them into two separate streams and once the original stream complete, re-combine their final emitted value (or get undefined if none exists).
e.g
const split1$ = source$.pipe(
filter(m) => m.kind === 1,
mergeMap(m) => someProcessing1());
const split2$ = source$.pipe(
filter(m) => m.kind === 2,
mergeMap(m) => someProcessing2());
forkJoin(split1$, split2$).subscribe(
(output1, output2) => console.log(output1, output2));
The problem is that nothing guarantee that both split1$ and split2$ will emit values. If that happens, forkJoin never emits.
With what can I replace forkJoin to emit a value whenever the source stream completes.
About splitting the stream:
https://www.learnrxjs.io/operators/transformation/partition.html
About "emit on complete", can't you just use complete callback instead? .subscribe(() => console.log('Emitted"), null, () => console.log('Completed'));
Otherwise you can use startWith operator to make sure something was emitted.
const [evens, odds] = source.pipe(partition(val => val % 2 === 0));
evens = evens.pipe(startWith(undefined)); // This will emit undefined before everything, so forkJoin will surely emit
Add startWith in the forkJoin constructor:
forkJoin(evens.pipe(startWith(undefined)), odds.pipe(startWith(undefined)))
.subscribe(console.log))
I have the following methods which should be called like this:
registerDomain should be called and should return an operationId
After 10 seconds, getOperationDetail should be called passing in the operationId
getOperationDetail should be called every 10 seconds until successful is returned.
Once getOperationDetail finishes, createRecordSets should be called.
Finally, getChangeStatus should be called until it returns INSYNC
If any of the api calls throw an exception, how can I handle the error on the client side?
The following code below calls registerDomain and getOperationDetail, but after getOperationDetail completes, it does not move onto createRecordSets.
registerDomain(domain) {
return this._adminService.registerDomain(domain)
.concatMap(operation => this.getOperationDetail(operation.OperationId))
.concatMap(() => this._adminService.createRecordSets(domain));
}
getOperationDetail(operationId) {
return Observable.interval(10000)
.mergeMap(() => this._adminService.getOperationDetail(operationId))
.takeWhile((info) => info.Status.Value !== 'SUCCESSFUL');
}
createRecordSets(caseWebsiteUrl) {
return this._adminService.createRecordSets(caseWebsiteUrl.Url)
.concatMap(registerId => this.getChangeStatus(registerId));
}
getChangeStatus(registerId) {
return Observable.interval(5000)
.mergeMap(() => this._adminService.getChange(registerId))
.takeWhile((info) => info.ChangeInfo.Status.Value !== 'INSYNC');
}
I updated getOperationDetail to use the first operator:
getOperationDetail(operationId) {
return Observable.interval(3000)
.mergeMap(() => this._adminService.getOperationDetail(operationId))
.first((info) => info.Status.Value === 'SUCCESSFUL')
}
Now it does in fact call createRecordSets, however, after createRecordSets, it continues to call getOperationDetail about 13 times and eventually calls getChangeStatus. The way I was looking at it, I thought it would:
Call getOperationDetail until it receives a SUCCESS.
Call createRecordSets one time.
Call getChangeStatus until it receives an INSYNC
Done.
Why the additional calls?
I changed the registerDomain to look like this:
registerDomain(domain) {
return this._adminService.registerDomain(domain)
.concatMap(operation => this.getOperationDetail(operation.OperationId))
.concatMap((op) => this.createRecordSets(op));
Before I had the .concatMap((op) => this.createRecordSets(op)) chained right after this.getOperationDetail. Once I moved it outside that, it started working as expected. I am unsure why though. Can someone explain?
When takeWhile meets a value that satisfies a specified criteria, it completes the observable without propagating the value. It means that the next chained operator will not receive the value and will not invoke its callback.
Suppose that in your example the first two calls to this._adminService.getOperationDetail(...) result in a non-successful status and the third call succeeds. It means that an observable returned by getOperationDetail() would produce only two info values each of which having non-successful status. And what might be also important, the next chained concatMap operator would invoke its callback per each of those non-successful values, meaning that createRecordSets() would be called twice. I suppose that you might want to avoid that.
I would suggest to use first operator instead:
getOperationDetail(operationId) {
return Observable.interval(10000)
.concatMap(() => this._adminService.getOperationDetail(operationId))
.first(info => info.Status.Value !== 'SUCCESSFUL');
}
This way getOperationDetail() would produce only a single "successful" value as soon as this._adminService.getOperationDetail(operationId) succeeds. The first operator emits the first value of the source observable that matches the specified condition and then completes.
And when it comes to error handling, catch or retry operators might be useful.
Update:
The unexpected behavior you have faced (getOperationDetail() keeps being called after first() completes) seems to be a bug in rxjs. As described in this issue,
every take-ish operator (one that completes earlier than its source Observable), will keep subscribing to source Observable when combined with operator that prolongs subscription (here switchMap).
Both first and takeWhile are examples of such take-ish operators and operators that "prolong" subscription are, for example, switchMap, concatMap and mergeMap. In the following example numbers will be kept logging while inner observable of concatMap is emitting values:
var takeish$ = Rx.Observable.interval(200)
// will keep logging until inner observable of `concatMap` is completed
.do(x => console.log(x))
.takeWhile(x => x < 2);
var source = takeish$
.concatMap(x => Rx.Observable.interval(200).take(10))
.subscribe();
It looks like this can be worked around by turning an observable containing such a take-ish operator into a higher-order observable — in a similar way as you've done:
var takeish$ = Rx.Observable.interval(200)
// will log only 0, 1, 2
.do(x => console.log(x))
.takeWhile(x => x < 2);
var source = Rx.Observable.of(null)
.switchMap(() => takeish$)
.concatMap(x => Rx.Observable.interval(200).take(1))
.subscribe();
Update 2:
It seems that the bug described above still exists as of rxjs version 5.4.2. It affects, for example, whether or not the source observable of the first operator will be unsubscribed when first meets the specified condition. When first operator is immediately followed by concatMap, its source observable will not be unsubscribed and will keep emitting values until inner observable of concatMap completes. In your case it means that this._adminService.getOperationDetail() would keep being called until observable returned by createRecordSets() would have completed.
Here's your example simplified to illustrate the behavior:
function registerDomain() {
return Rx.Observable.of("operation")
.concatMap(() => getOperationDetail()
.concatMap(() => Rx.Observable.interval(200).take(5)));
}
function getOperationDetail() {
return Rx.Observable.interval(100)
// console.log() instead of the actual service call
.do(x => console.log(x))
.first(x => x === 2);
}
registerDomain().subscribe();
<script src="https://unpkg.com/#reactivex/rxjs#5.0.3/dist/global/Rx.js"></script>
If we expand the inner observable of the first concatMap operator, we will get the following observable:
Rx.Observable.interval(100)
.do(x => console.log(x))
.first(x => x === 2)
.concatMap(() => Rx.Observable.interval(200).take(5));
Notice that first is immediately followed by concatMap which prevents the source observable of the first operator (i.e. interval(100).do(x => console.log(x)) from being unsubscribed. Values will keep being logged (or in your case, service calls will keep being sent) until the inner observable of concatMap (i.e. interval(200).take(5)) completes.
If we modify the example above and move the second concatMap out of the inner observable of the first concatMap, first will not be chained with it any more and will unsubscribe from the source observable as soon as the condition is satisfied, meaning that interval will stop emitting values and no more numbers will be logged (or no more service requests will be sent):
function registerDomain() {
return Rx.Observable.of("operation")
.concatMap(() => getOperationDetail())
.concatMap(() => Rx.Observable.interval(200).take(5));
}
function getOperationDetail() {
return Rx.Observable.interval(100)
// console.log() instead of the actual service call
.do(x => console.log(x))
.first(x => x === 2);
}
registerDomain().subscribe();
<script src="https://unpkg.com/#reactivex/rxjs#5.0.3/dist/global/Rx.js"></script>
Inner observable in such a case can be expanded simply to:
Rx.Observable.interval(100)
.do(x => console.log(x))
.first(x => x === 2)
Notice that first is not anymore followed by concatMap.
It is also worth mentioning that in both cases observable returned by registerDomain() produces exactly the same values and if we move logging from do() operator to subscribe(), the same values will be written to the console in both cases:
registerDomain.subscribe(x => console.log(x));
I am moving from the Promise world to the Observable world. One thing I usually do with Promise is to chain a series of tasks and make them run in sequence. For example, I have three tasks: printLog1() to print 1 to the console, printLog23() to print 2 and 3 to the console, and printLog4() to print 4.
When I want to print 1-2-3-4, I would write a promise chain like
printLog1()
.then(() => {
printLog23();
})
.then(() => {
printLog4();
});
Now I want the same functionality with Observable and I can rewrite the printLog() function into an Observable like
printLog1 = Rx.Observabale.of(1).map((i) => console.log(i));
printLog23 = Rx.Observabale.of(2, 3).map((i) => console.log(i));
printLog4 = Rx.Observabale.of(4).map((i) => console.log(i));
Then I have three observables that emits different values to the console. How do I chain them so that these three observables would run in order and print 1-2-3-4?
If you want to be sure the order of emissions is the same as the order in which you specified the source Observables you can use concat or concatMap operators.
The concat* operators subscribe to an Observable only after the previous Observable completes (it works with Promises as well, see http://reactivex.io/rxjs/class/es6/MiscJSDoc.js~ObservableInputDoc.html).
In you case it'd look like the following:
import { concat } from 'rxjs'; // Note, concat from 'rxjs', is not the same as concat from 'rxjs/operators'
concat(printLog1, printLog23, printLog4);
... or with concatMap if the request for one Promise depends on the response from the previous Promise:
printLog1.pipe(
concatMap(response => ...),
concatMap(response => ...),
);
... or when the order doesn't matter you can use merge that subscribes to all Observables/Promises immediately and reemits their results as they arrive:
merge(printLog1, printLog23, printLog4);
Jan 2019: Updated for RxJS 6
My solution:
const sequence: Observable<any>[] = [of(1), of(2), of(3)];
return concat(...sequence).pipe(toArray());