我需要从一个可观察的项目中听取一组项目。当某些条件出现时,将对该项执行异步任务,并且该组件将“忙”,直到完成为止。我想暂停处理订阅中的项目,直到此任务完成(因为以下项目的处理取决于结果),然后从序列中的下一个项目继续而不会有任何损失。
下一部分可能是最好的阅读,同时看看Plunk here
为实现这一目标,我使用buffer和swtichMap。我认为这些可以自己完成这项工作,但是switchMap会破坏并重新创建每次重置序列的订阅。
export class AppComponent implements OnInit {
source$: Observable<any>;
clearBuffer$ = new Subject();
busy$ = new Subject();
private itemSubscription: Subscription;
private stayAliveSubscription: Subscription;
items: any[] = [];
constructor() { }
ngOnInit() {
this.source$ = Observable.range(1, 500).zip(
Observable.interval(500),
function (x, y) { return x; }
).share();
this.busy$
.subscribe(result => {
if (!result) {
this.clearBuffer$.next();
}
}, error => {
console.log(error);
});
}
start() {
if (!this.itemSubscription) {
this.itemSubscription =
this.busy$.switchMap(busy => {
if (busy) {
return this.source$.buffer(this.clearBuffer$);
} else {
return this.source$;
}
})
.subscribe(items => {
if (Array.isArray(items)) {
this.items.push('buffered: ' + items.join());
} else {
this.items.push('live feed: ' + items);
}
}, error => {
this.items.push(error);
});
this.stayAliveSubscription = this.source$
.subscribe(result => {
console.log(result);
}, error => {
console.log(error);
});
this.busy$.next(false);
}
}
...
}
要解决此问题,现在共享源$ observable,并启动单独的订阅( stayAliveSubscription ),以便始终使用单个订阅。这对我来说似乎很混乱,我想问一下是否有人可以向我展示更好/替代方法来解决这个问题。
我将工作样本放入Plunk here点击开始以启动订阅,然后将忙碌切换设置/取消设置为缓冲并继续。
编辑:使用concatMap工作代码
我更改了Plunk以使用concatMap。我也粘贴了下面的代码。关键是concatMap中返回的busy observable必须完成,你不能多次返回busy $ observable,并在忙状态改变时调用next。
source$: Observable<any>;
busy$ = new Subject();
busy: boolean;
private itemSubscription: Subscription;
private stayAliveSubscription: Subscription;
items: any[] = [];
constructor() { }
ngOnInit() {
this.source$ = Observable.range(1, 500).zip(
Observable.interval(500),
function (x, y) { return x; }
);
this.busy$
.subscribe(busy => {
this.busy = <any>busy;
});
}
start() {
if (!this.itemSubscription) {
this.itemSubscription = this.source$.concatMap(item => {
const busySubject = new Subject();
this.busy$
.subscribe(result => {
busySubject.next(item);
busySubject.complete();
});
if (this.busy) {
return busySubject;
} else {
return Observable.of(item);
}
})
.subscribe(item => {
this.items.push(item);
}, error => {
this.items.push(error);
});
}
this.setBusy(false);
}
答案 0 :(得分:2)
我不完全明白你要做什么,但是如果只是在&#34;异步任务&#34;中保留发出值的顺序的问题。可能需要很长(随机)的时间,我想你可以使用concatMap
运算符。
concatMap
将每个源值投影到Observable,Observable在输出Observable中合并,以序列化方式等待每个源在合并下一个之前完成。
在此示例中,src
Observable每隔100ms发出一次值,每个值都映射到一个新的observable,该observable发出0到2000ms之间的值(异步任务)。您可以看到订单是安全的。
let src = Rx.Observable.timer(0,100);
src.concatMap(i=>{
return Rx.Observable.timer(Math.random()*2000).mapTo(i); // this is the async task
}).subscribe(data=>console.log(data));
&#13;
<script src="https://unpkg.com/rxjs@5.4.0/bundles/Rx.min.js"></script>
&#13;
您也不应该使用这些订阅来生成可观察的发射数据。实际上,您应该使用.publish()
和.connect()
代替share()
和subscribe()
转换cold observable to a hot one:
this.source$ = Observable.range(1, 500).zip(
Observable.interval(500),
function (x, y) { return x; }
).publish();
// blah blah blah some code
this.source$.connect();
答案 1 :(得分:1)
delayWhen
是非常强大的运算符。我的解决方案使用mergeMap
和delayWhen
。
功能:重试,限制,暂停,恢复
const concurrentLimit = 5
const retryLimit = 10
const source$ = from(new Array(100).fill(0).map((_, i) => i))
// remove <boolean> if not typescript
const pause$ = new BehaviorSubject<boolean>(false);
const pass$ = pause$.pipe(filter((v) => !v));
const throttledTask$ = source$.pipe(
mergeMap((item) => {
return of(item).pipe(
delayWhen(() => pass$),
mergeMap(async (item) => {
// you can also throw some errors
return await new Promise((resolve)=>
setTimeout(resolve(item), Math.random()*1000))
}),
retryWhen((errors$) => errors$.pipe(delay(1000), take(retryLimit)))
);
}, concurrentLimit)
const subscription = throttledTask$.subscribe(x => console.log(x))
const pause = () => { pause$.next(true) }
const resume = () => { pause$.next(false) }
说明:
delayWhen
将暂停流,并等待直到pass$
信号发出。BehaviorSubject
用于构成pass$
信号,订阅时将发出最后一个值。mergeMap
可以处理异步任务,并具有并发线程数限制参数。当delayWhen
暂停流时,该流将保留在mergeMap
内并占用并发的“线程”。retryWhen
将重新订阅,直到errors$.pipe(delay(1000), take(retryLimit))
发出完整或错误消息为止。