问题
The following
Rx.Observable.zip(
Rx.Observable.of(1,2),
Rx.Observable.of("a"))
.subscribe(p => console.log(p))
produces
1,a
which makes sense, but what I want it to produce is
1,a
2,undefined
I want to pad the shorter observable with undefineds until the longer one completes. Any suggestions?
回答1:
I realise adding delay can turn the .of operator async and with scan you can replace the same value with undefine
Rx.Observable.combineLatest(
Rx.Observable.of(1,2).delay(0),
Rx.Observable.of("a"))
.scan((acc,curr)=>{
acc[1]=acc[1]==curr[1]?undefined:curr[1]
acc[0]=acc[0]==curr[0]?undefined:curr[0]
return acc
},[])
.subscribe(p => console.log(p))
回答2:
I think the key to this is to ensure that all of the source observables are the same length.
One solution would be to compose a counter observable that's as long as the longest source observable. It could then be concatenated to the shorter source observables, like this:
const pad = (...sources) => Rx.Observable.create(observer => {
// Publish the source observables to make them multicast
// and to allow the subscription order to be managed.
const publishedSources = sources.map(source => source.publish());
// Create an observable that emits an incremented index and
// is as long as the longest source observable.
const counter = Rx.Observable
.merge(...publishedSources.map(
source => source.map((unused, index) => index)
))
.scan((max, index) => Math.max(max, index), 0)
.distinctUntilChanged()
.publish();
// Zip the published sources, contatenating the counter so
// that they are all the same length. When the counter
// emissions are concatenated, they are mapped to undefined.
const subscription = Rx.Observable.zip(...publishedSources.map(
source => source.concat(counter.mapTo(undefined))
)).subscribe(observer);
// Connect the counter and the published sources.
subscription.add(counter.connect());
publishedSources.forEach(
source => subscription.add(source.connect())
);
return subscription;
});
pad(
Rx.Observable.of(1, 2),
Rx.Observable.of("a")
).subscribe(padded => console.log(padded));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs@5/bundles/Rx.min.js"></script>
回答3:
My own solution is below. The idea is that all the observables are converted into endless streams, starting with the original values, enclosed in objects, and then an infinite number of undefineds. These endless streams are concatted and the result is taken as long as any of the values are original.
const unending = (obs) =>
obs.map(value => ({value}))
.concat(Rx.Observable
.interval(0)
.mapTo(undefined));
const zipPad = (...obss) =>
Rx.Observable.zip(...obss.map(unending))
.takeWhile(p => p.find(v => v))
.map(p => p.map(v => v && v.value));
zipPad(Rx.Observable.of(1,2,3),
Rx.Observable.of("a"),
Rx.Observable.of("x", "y"))
.subscribe(p => console.log(p));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.6/Rx.min.js"></script>
Anyone is welcome to improve on this answer and post their variations here.
来源:https://stackoverflow.com/questions/48103670/padding-zip-with-undefined