rxjs - 在observables中搜索

时间:2016-08-08 06:05:02

标签: search rxjs

我正在尝试实现一个简单的搜索方案,以从另一个observable中搜索一个observable中的值。下面的buildLookup函数使用来自observable:

的值构建查找表
// Build lookup table from an observable. 
// Returns a promise
function buildLookup(obs, keyName, valName) {
    const map = new Map();
    obs.subscribe((obj) => map.set(obj[keyName], obj[valName]));

    // use concat to force wait until `obs` is complete
    return obs.concat(Observable.from([map])).toPromise();
}

然后我有另一个函数,它使用了这个函数的结果(一个promise):

// Lookup in a previously built lookup table.
function lookup(source, prom, keyName, fieldName) {
    return source.map((obj) => {
        const prom2 = prom.then((map) => {
            return lodash.assign({}, obj, { [fieldName]: map.get(String(obj[keyName])) });
        });
        return Observable.fromPromise(prom2);
    })
    .flatMap((x) => x);
}

由于某种原因,此实现不起作用,并且所有其他查找似乎都失败了。你能指导我一下吗?

  • 此代码有什么问题,
  • 是否有更好的方法来实现这样的东西?

提前感谢您的帮助!

我在下面附上我的测试代码:

"use strict";
const lodash = require("lodash");
const rxjs = require("rxjs");
const chai = require("chai");

const Observable = rxjs.Observable;
const assert = chai.assert;
const assign = lodash.assign;

describe("search", () => {
    it("simple search", (done) => {
        let nextId = 1, nextId2 = 1;
        const sourceObs = Observable.interval(5).take(5).map((i) => {
            const id = nextId++;
            return { id: `${id}` };
        });

        const searchableObs = Observable.interval(5).take(5).map((i) => {
            const id = nextId2++;
            return Observable.from([
                { id: `${id}`, code: "square", val: id * id },
            ]);
        }).flatMap((x) => x);


        const results = [];
        const verifyNext = (x) => {
            assert.isDefined(x);
            results.push(x);
        };
        const verifyErr = (err) => done(err);
        const verifyComplete = () => {
            assert.equal(results.length, 5);
            try {
                results.forEach((r) => {
                    console.log(r);
                    // assert.equal(r.val, r.id*r.id);  <== *** fails ***
                });
            } catch (err) {
                done(err);
            }
            done();
        };

        // main
        const lookupTbl = buildLookup(searchableObs, "id", "val"); // promise that returns a map
        lookup(sourceObs,  lookupTbl, "id", "val")
            .subscribe(verifyNext, verifyErr, verifyComplete)
            ;
    });

});


// output
// { id: '1', val: 1 }
// { id: '2', val: undefined }
// { id: '3', val: 9 }
// { id: '4', val: undefined }
// { id: '5', val: 25 }

1 个答案:

答案 0 :(得分:0)

所以,这里有很多要解决的问题。

主要问题是你在sourceObssearchableObs可观测量中发生了副作用,并且没有发布,所以副作用会多次发生,因为你多次订阅,给出你完全错了地图。例如,我得到的地图如下:

{"1" => 1, "4" => 16, "7" => 49, "12" => 144}

但是你做的事情是如此微不足道,以至于你应该使用可变变量。

要解决此问题,可以使用以下方法创建正确的observable:

const sourceObs = Rx.Observable.range(1, 5).map(i => ({ id: `${i}` }));

const searchableObs = Rx.Observable.range(1, 5).map(i =>
  ({ id: `${i}`, code: "square", val: i * i })
);

没有理由使用变量,因为range返回数字1,2,... 您对o.map(_ => Rx.Observable.from(...)).concatMap(e => e)的使用与o ...

完全相同

虽然我在这里,但这是你正确但笨拙的功能的简化版本:

// so buildLookup just needs to return a map once it's finished populating it
function buildLookup(obs, keyName, valName) {
  // following your style here, though this could be done using `scan`
  const map = new Map();
  obs.subscribe((obj) => map.set(obj[keyName], obj[valName]));
  // instead of your promise, I just wait for `obs` to complete and return `map` as an observable element
  return obs.ignoreElements().concat(Rx.Observable.of(map));
}

// and lookup just needs to wait for the map, and then populate fields in the object    
function lookup(source, prom, keyName, fieldName) {
  return prom
    .concatMap(map => source.map(obj => ({ obj: obj, map: map })))
    .map(({ obj, map }) => lodash.assign({}, obj, { [fieldName]: map.get(String(obj[keyName])) }))
  ;
}

这应该适合你。