数组上的Observable concat方法返回相同的数组

时间:2017-02-03 09:24:39

标签: angular typescript rxjs observable rxjs5

在我的服务中,我尝试解决下一个问题。我有一个json文件,其中包含其他json文件(卡片)的名称:

{
  "filename1" : ... ,
  "filename2" : ... ,
  ...
  "filenameN" : ...
}

我需要加载所有文件。 " filenameX"文件有一些卡片数据:

{
  dataX
}

我需要在对象中组合加载的数据:

{
  "filename1" : { data1 },
  "filename2" : { data2 },
...
  "filenameN" : { dataN }
}

我为任何文件加载创建Observer,并尝试将它们组合到一个高级单个Observer,当所有相应的Observer都被解析时。这是我的代码:

import { Observable } from "rxjs/Observable";
import "rxjs/add/observable/concat";
import "rxjs/add/operator/map";
...
    _loadCards(dir, cardsListFile) {
        var http = ...
        var url = ...
        return Observable.create(function (observer) {
            http.get(url + dir + "/" + cardsListFile + ".json").map(res => { return res.json(); }).subscribe(list => {
                let data = {};
                let observers = [];
                for(var card in list) {
                    if(list.hasOwnProperty(card)) {
                        let obs = http.get(url + dir + "/cards/" + card + ".json").map(res => { return res.json(); });
                        observers.push(obs);
                        let getData = function(card) {
                            obs.subscribe(cardData => {
                                data[card] = cardData;
                            });
                        };
                        getData(card);
                    }
                }

                let concatResult = Observable.concat(observers);
                console.log(concatResult);
                concatResult.subscribe(result => {
                    observer.onNext(data);
                    observer.onCompleted();
                });         
            });
        });
    };

然而,Observer的concat运算符并没有像所描述的那样工作 - 它返回与其输入相同的Observers数组。问题出在哪里以及我可以使用哪些其他操作员来使我的解决方案更直接(因为现在它的确很难看)?

3 个答案:

答案 0 :(得分:2)

JSBIN与解决方案:http://jsbin.com/dahaqif/edit?js,console

我假设您有两个observable,一个用于获取文件名列表,另一个用于获取特定文件名的数据:

const filenamesObs = Observable.of({
  "filename1" : null,
  "filename2" : null,
  "filename3" : null
});
// This is actually a function that returns an observable.
const filedataObs = (filename) => {
  return Observable.of(`This is the data for ${filename}`);
}

现在,您可以在这里结合两个可观察对象来获取您描述的数据结构:

const source = filenamesObs

  // Extract the list of filenames as an array.
  .map(obj => Object.keys(obj))

  // Flatten the array, i.e. emit each filename individually
  // vs a SINGLE array containing ALL filenames.
  .mergeMap(val => val)

  // Get the data for each filename.
  .mergeMap(filename =>
    filedataObs(filename).map(data => Object.assign({}, { filename: filename, data: data }))
  )

  // At this point, you have a stream of { filename, data } objects.
  // Reduce everything back to a single object.
  .reduce((acc, curr) => {
    acc[curr.filename] = curr.data;
    return acc;
  }, {});

如果您订阅并将结果记录到控制台,您将看到:

[Object] {
  filename1: "This is the data for filename1",
  filename2: "This is the data for filename2",
  filename3: "This is the data for filename3"
}

Arseniy的评论之后的其他说明

  • 第一个mergeMap()是"技巧"将我们在开头的SINGLE数组转换为MULTIPLE,INDIVIDUAL值。由于您希望能够单独处理每个文件名(以获取相应的数据),因此逐个接收文件名与包含所有文件名的大数组相比更方便。为了清楚起见,你应该去我的JSBIN并在第一个.do(console.log)之后添加第mergeMap() 之后的。您将立即了解其中的差异。
  • 第二个mergeMap()"项目"可观察到的源的值可观察到目标可观察量。它听起来很奇特,但它只是意味着我们正在将文件名(第一个可观察的)转换为HTTP请求以获取文件'数据(第二个可观察的)。
  • 最后,Object.assign()让我们相遇,让所有东西重新组合在一起。由于我们有来自两个可观察数据的数据 - 发出文件名的文件和发出文件数据的数据 - 我们需要先将所有内容合并到一个容器中,然后再使用它(或进一步转换)。为此,我使用Object.assign()创建了一个包含两个属性filenamedata的临时对象。请注意,这是vanilla JavaScript,它与observables无关。同样,您可以在第二个.do(console.log)之后添加mergeMap()行,以便在此时向控制台打印流包含的内容。

答案 1 :(得分:1)

规则#1:从不在其他订阅中使用自定义订阅 - 可能有0.1%的情况需要这样做。

我已经重新编写了一些关于如何完成的流:

_loadCards(dir, cardsListFile) {
    var http = ...
    var url = ...
    return http.get(url + dir + "/" + cardsListFile + ".json")
        .map(res => res.json())
        .switchMap(fileMap => Rx.Observable.from(Object.keys(fileMap)))
        .concatMap(card => http.get(url + dir + "/cards/" + card + ".json")
               .map(res => ({[card]: res.json()})))
        .map(arrayOfCardResultObjects => Object.assign.apply({}, arrayOfCardResultObjects));
};

此调整后的版本可作为片段直播:

getMockedCardMap()
    .switchMap(fileMap => Rx.Observable.from(Object.keys(fileMap)))
    .concatMap(card => getMockedCardFromFile(card).map(res => ({[card]: res})))
    .toArray()
    .map(arrayOfCardResultObjects => Object.assign.apply({}, arrayOfCardResultObjects))
    .subscribe(console.log);


// Mocked helper-methods
function getMockedCardMap() {
    return Rx.Observable.of({
        "file1": "foo",
        "file2": "bar",
        "file3": "baz"
    });
}

function getMockedCardFromFile(file) {
    return Rx.Observable.of("Result from: " + file);
}
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>

答案 2 :(得分:1)

我不完全确定我理解你想要做什么,但concat()运算符将其参数解压缩而不是数组。

这意味着@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); String intentaction = intent.getAction(); if (intentaction != null) { if (intentaction.equals("tab2deposit")) { mViewPager.setCurrentItem(1); } } } 只会重新发送Observable,因为它们位于Observable.concat(observables)数组中。

你想要的是传递数组解压缩observables,它与:

相同
Observable.concat(...observables)

我认为这可以模拟您尝试做的事情。它使用Observable.concat(observable1, observable2, observable3 ,...) 代替forkJoin,因此等待所有Observable完成。

concat

打印到控制台:

var input = {
  "filename1": 'data1',
  "filename2": 'data2',
  "filenameN": 'dataN'
};

let keys = Object.keys(input);

let observables = keys.map(key => {
  return Observable.of(input[key]).map(s => s.split("").reverse().join(""));
});

Observable.forkJoin(observables, (...results) => {
    let combined = {};
    keys.forEach((key, i) => {
      combined[key] = results[i];
    });
    return combined;
  })
  .subscribe(val => console.log(val));

查看现场演示:https://jsbin.com/coruyu/4/edit?js,console