使用mergeMap进行分解并转换

时间:2018-03-01 14:50:41

标签: rxjs rxjs5

我有一个带有publishReplay和refcount的Subject,所以它实际上永远不会完成。 refcount的输出是一个Observable对象数组。我想将Observable对象数组分解为发出的元素,根据需要进行转换,然后将它们合并回数组,但由于源未完成,因此异步性质中的数组边界“丢失”。

作为一个例子,我使用mergeMap和scan来累积数据。但是,正如预期的那样,它会不断累积,因此生成的数组是流的整个历史记录。扫描的初始值仅在流的开头初始化。我不能使用toArray,因为Observable永远不会终止。

我在异步硬件设计方面有很多经验。这个异步链的硬件类似于使用锁存器。我不确定RxJS中概念等价物是什么。我会假设从refcount获取发出的数组输出并将其应用于Observable.from(theArrayOutput)将起作用,但我无法弄清楚如何将它插入到流链中。

import {Component, OnInit} from '@angular/core';
import {Observable} from 'rxjs/Observable';
import {Subject} from 'rxjs/Subject';

type IObjectsOperation = (types: Object[]) => Object[];

@Component({
    selector: 'app-events-test',
    templateUrl: './events-test.component.html',
    styleUrls: ['./events-test.component.css']
})

export class EventsTestComponent implements OnInit {
    public objects: Observable<Object[]>;
    public scanned: Observable<any>;
    protected updates: Subject<any> = new Subject<any>();
    protected functionStream: Subject<any> = new Subject<any>();
    protected addStream: Subject<Object> = new Subject<Object>();
    private initialObjects: Object[] = [1];

    constructor() {
        this.objects = this.updates
            .scan((objects: Object[],
                   operation: IObjectsOperation) => {
                    return operation(objects);
                },
                this.initialObjects)
            .publishReplay(1)
            .refCount();
        this.functionStream
            .map(function (message: Object): IObjectsOperation {
                return (messages: Object[]) => {
                    return messages.concat(message);
                };
            })
            .subscribe(this.updates);
        this.addStream.subscribe(this.functionStream);
        this.scanned = this.objects
            .mergeMap(val => val)
            // some transformation that I want to have happen, in this case no-op
            .filter(() => {return true})
            // attempt to rebuild array, but items are accumulated
            .scan( (acc: Array<any>, x: Object) => { return acc.concat(x); }, [])

    }


transform(objects) {
    return Observable.from(objects)
        // this withLatestFrom suggestion didn't work
        // .withLatestFrom( this.functionStream, ( val, fn ) => fn( val ) )
        .filter(() => {
            return true
        })
        .toArray()
}


    start(): void {
        console.log('---------STARTING');
        this.objects.mergeMap(obj => this.transform(obj))
        .subscribe(
            obj => console.log('objects: ' + obj)
        );
        this.scanned.subscribe(
            {
                next: obj => {
                    console.log('scanned: ' + obj);
                },
                error: () => {
                },
                complete: () => console.log('COMPLETED')
            }
        );
        this.add(2);
        this.add(3);
    }
    add(object: Object): void {
        this.addStream.next(object);
    }
    ngOnInit() {
        this.start();
    }

}

输出低于预期值。

---------STARTING
events-test.component.ts:49 objects: 1,2
events-test.component.ts:52 scanned: 1
events-test.component.ts:52 scanned: 1,2
events-test.component.ts:49 objects: 1,2,3
events-test.component.ts:52 scanned: 1,2,1
events-test.component.ts:52 scanned: 1,2,1,2
events-test.component.ts:52 scanned: 1,2,1,2,3

我希望看到的是:

---------STARTING
events-test.component.ts:49 objects: 1,2
events-test.component.ts:52 scanned: 1,2
events-test.component.ts:49 objects: 1,2,3
events-test.component.ts:52 scanned: 1,2,3

我可以使用一些解决方法:

  1. 将数据保持为整个流中的数组
  2. 将源不完整的observable转换为完成
  3. 我认为这种架构的解决方案是:

    1. 我可以在流中插入一些操作从对象Observable中获取数组,并返回一个完成的observable
    2. 需要从源Observable发出一些外部信号,指示“处理”,然后终止限制扫描。这对我来说似乎很笨拙。
    3. 我认为在RxJS中会有一些等效的异步硬件锁存器,所以我想保留当前的架构。另外,我认为RxJS非常酷,并希望在基于时间的流处理方面缩小我的知识差距。

      编辑:@xtianjohns的一个很好的答案表明了如何进行内循环,但订阅仍然没有完成。建议添加withLatestFrom导致转换函数中断,在注释掉该行时,数组将被渲染,但外部循环不会完成。如果该行存在,则不呈现该数组,并且循环未完成。

1 个答案:

答案 0 :(得分:1)

您的问题来自于使用mergeMap后跟scan,并且在“外部”流上执行此操作,而不是转换对象列表然后发出该列表。

另一种方式:如果你想要的是一个数组流,那么让它始终保持一个数组流。

        /* Function that takes array and returns stream of transformed arrays */
        function transform( objects ) {
          return Observable.from( objects )
            /* We only need this because your transformation is wrapped in a stream. */
            .withLatestFrom( this.functionStream, ( val, fn ) => fn( val ) )
            // some transformation that I want to have happen, in this case no-op
            .filter(() => {return true});
            .toArray();
        }

        this.scanned = this.objects
          /* let's keep this as Observable<Array<YourObject>> */
          .mergeMap( transform );
          /* No need to "rebuild" array, this is now a stream of arrays */

注意在这个例子中,我“转换”transform内部数组的元素,而不是试图在流中“解包”数组,然后重构。

这是否适合您的使用案例?

编辑:我在读取你的functionStream签名时犯了一个错误,已经纠正了这一点。