I have an array of observables which I'm executing in parallel using:
let observables: Observable<any>[]
Observable.forkJoin(observables)
This works perfectly, however, I need to execute the array of observables sequentially, and only emit one result if the last observable has been completed. That's when I tried to use
Observable.concat(observables)
But this returns multiple results, and not only one - combined - result which I get when using forkJoin. So I actually need a combination of the two.
I've tried to use the reduce functionality to execute them sequentially, like this:
return observables.reduce((previous, current) => {
return previous.flatMap(() => current);
}, Observable.empty());
But with this solution the observables are not executed at all.
Assuming that your observables emit singular values, not arrays, you could rework your current approach to something like:
return Observable.concat(...observables).reduce((acc, current) => [...acc, current], []);
or even shorter:
return Observable.concat(...observables).toArray();
In the case that they emit array values, you could do the following:
const source = Observable.concat(...observables).flatMap(list => list).toArray();
As Jota.Toledo or Mateusz Witkowski showed in their answers, with the new syntax of RxJS you can do:
return concat(...observables).pipe(toArray());
You can you use toArray() operator:
Observable.concat(observables).toArray().subscribe()
As stated in RxJS documentation: it creates "an observable sequence containing a single element with a list containing all the elements of the source sequence".
Related
I use the following code in an angular app. I used the RxJS map call similar to how array map is used. After reading about RxJS switchmap operator, I'm not sure whether I should use map or switchmap. Should I use switchmap so the observable which is returned from the http call is closed so there is no memory leak?
getPeopleForTypeahead(term: string): Observable<IPersonForTypeahead[]> {
var peopleUrl = `https://localhost:5001/api/peoplesearch?name=${term}`;
return this.http.get<any>(peopleUrl)
.pipe(
map(pl => {
return this.peopleAsFlattened(pl.peopleList).reduce((p, c) => p.concat(c));
}),
catchError(this.handleError('getPeopleForTypeahead', []))
);
}
peopleAsFlattened = (pla: IPeopleList[]) => {
return pla.map(pl => pl.people.map(p => {
return {
id: p.id,
fullName: p.fullNames[0].firstName + " " + p.fullNames[0].lastName
};
}));
}
map and switchMap have completely different purposes:
map - transform the shape of an emission
switchMap - subscribe to an observable and emit its emissions into the stream
map
Use map when you want transform the shape of each emission. Ex: emit the user name property, instead of the entire user object:
userName$: Observable<string> = this.service.getUser(123).pipe(
map(user => user.name)
);
switchMap
Use switchMap when you want to map an emission to another observable and emit its emissions. Ex: You have an observable of some id and want to emit the resource after fetching it:
user$: Observable<User> = this.userId$.pipe(
switchMap(id => this.service.getUser(id)),
);
When user$ is subscribed to, the user returned from service.getUser(id) is emitted (not the userId string).
switchMap is not interchangeable with the map operator, nor vise versa. Although both of them has to do with mapping (as their names suggest), they have two separate use-cases.
In your particular case, the map operator is the way to go.
When to use switchMap?
You can only use switchMap(cb) when you check all these requirements:
Your callback function, cb, passed into switchMap returns an observable, observable$.
If your cb (callback function) does not return an observable, you should look into operators that don't handle higher-level observables, such as filter and map (what you actually needed); not operators that handle higher-level observables such as concatMap and well, switchMap.
You want to execute your cb sequentially before the next operation down the pipeline (after switchMap) executes.
Maybe you want to run logic inside of cb, and optionally get the return value of cb after executing, so that you can pass it down the pipeline for further processing, for example.
When you want to "discard" what will happen to cb's execution and re-execute cb every time the source observable (the thing that trickles down to switchMap(cb)) emits a new value/notification.
Applying what we hopefully learned, we know that your cb:
pl => {
return this.peopleAsFlattened(pl.peopleList).reduce((p, c) => p.concat(c));
}
returns a plain JavaScript array; not an observable. This takes using switchMap out of the question since it violates the first requirement I made up above.
Hopefully that makes sense. :)
We use switchMap when the source observable is a hot observable. In which case you prefer the behaviour that cancel the succeeding observable when source emits.
In your code, you source is a one-off http call which means it will not emit multiple times and the follow up action is not executing observable but to mutate an array. There is no need to use switchMap
I was originally searching how to return several actions in a ngrx effect, and found I need to return an array of actions.
Then I noticed that returning a simple array in the switchMap works as fine as returning an observable created from this array.
For example:
timer(1000).pipe(switchMap(val => from([val, val + 1)])).subscribe(val => console.log(val));
timer(1000).pipe(switchMap(val => [val, val + 1])).subscribe(val => console.log(val));
I expect the first to work and think it is the correct syntax.
I don't expect the second to work but it actually does and I would like to understand why.
Thanks,
Because switchMap, among other flattening-operators (mergeMap, exhaustMap, ...), takes an ObservableLike as the return type of its projection function.
An ObservableLike can be Observable, Promise or Array. If you provide an array, it is converted into a stream of its items - basically the same as if you had used from.
TLDR: Working example is in the last codeblock of this question. Check out #bryan60 answer for a working example using concat rather than mergeMap.
I'm trying to run a number of remote requests sequentially, but only the first observable is executed.
The number of request vary, so I can't do a dodgy solution where I nest observables within each other.
I'm using the following code:
const observables = [
observable1,
observable2,
...
];
from(observables).pipe(
mergeMap(ob=> {
return ob.pipe(map(res => res));
}, undefined, 1)
).subscribe(res => {
console.log('Huzzah!');
})
In the past (rxjs 5.5) Ive used the following:
let o = Observable.from(observables).mergeMap((ob) => {
return ob;
}, null, 1);
o.subscribe(res => {
console.log('Huzzah!');
})
I'm not sure what I'm doing wrong, can anybody shed some light?
An additional request would be to only print 'Huzzah!' once on completion of all requests rather than for each individual Observable.
EDIT:
Removing undefined from my original code will make it work, however there was another issue causing only the first observable to be executed.
I'm using Angular's HttpClient for remote requests. My observable code looked like this:
const observables = [];
// Only the first observable would be executed
observables.push(this.http.get(urla));
observables.push(this.http.get(urlb));
observables.push(this.http.get(urlc));
Adding .pipe(take(1)) to each observable results in each observable being executed:
const observables = [];
// All observables will now be executed
observables.push(this.http.get(urla).pipe(take(1));
observables.push(this.http.get(urlb).pipe(take(1));
observables.push(this.http.get(urlc).pipe(take(1));
The code I ended up using, which executes all observables in sequential order and only triggers Huzzah! once is:
const observables = [];
observables.push(this.http.get(urla).pipe(take(1));
observables.push(this.http.get(urlb).pipe(take(1));
observables.push(this.http.get(urlc).pipe(take(1));
from(observables).pipe(
mergeMap(ob=> {
return ob.pipe(map(res => res));
}, 1),
reduce((all: any, res: any) => all.concat(res), [])
).subscribe(res => {
console.log('Huzzah!');
})
Thanks to #bryan60 for helping me wit this issue.
if these are http requests that complete, I think your bug is caused by a change to the mergeMap signature that removed the result selector. it's hard to be sure without knowing exactly which version you're on as it was there, then removed, then added again, and they're removing it once more for good in v7.
if you want to run them sequentially... this is all you need...
// concat runs input observables sequentially
concat(...observables).subscribe(res => console.log(res))
if you want to wait till they're all done to emit, do this:
concat(...observables).pipe(
// this will gather all responses and emit them all when they're done
reduce((all, res) => all.concat([res]), [])
// if you don't care about the responses, just use last()
).subscribe(allRes => console.log(allRes))
In my personal utility rxjs lib, I always include a concatJoin operator that combines concat and reduce like this.
the only trick is that concat requires observables to complete till it moves on to the next one, but the same is true for mergeMap with concurrent subscriptions set to 1.. so that should be fine. things like http requests are fine, as they complete naturally after one emission.. websockets or subjects or event emitters will behave a bit differently and have to be manually completed, either with operators like first or take or at the source.
If you are not concerned about the sequence of execution and just want 'Huzzah!' to be printed once all the observable has been executed forkJoin can also be used.Try this.
forkJoin(...observables).subscribe(res => console.log('Huzzah');
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 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());