rxjs-observable不会发出任何内容(未完成?)

时间:2018-08-30 17:37:05

标签: angular group-by rxjs toarray

我有一个angular 6应用程序,最初是用rest api设置后端的,但是我开始转换零件以使用socket.io。

当我从rest api返回数据时,将执行以下操作:

this.http.get(api_url + '/versions/entity/' + entityId).pipe(
  mergeMap((versions:IVersion[]) => versions),
  groupBy((version:IVersion) => version.type),
  mergeMap(group => group.pipe(
    toArray(),
    map(versions=> {
      return {
        type: group.key,
        versions: versions
      }
    }),
    toArray()
  )),
  reduce((acc, v) => acc.concat(v), [])
);

快递路线:

router.get('/entity/:entityId', (req, res) => {
  const entityId = req.params.entityId;

  Version.getVersionsByEntity(entityId, (err, versions) => {
    if (err) {
      res.json({success: false, msg: err});
    } else {
      res.json(versions);
    }
  });
});

使用mongoose从我的mongo数据库中获取数据:

export function getVersionsByEntity(entityId, callback) {
  console.log('models/version - get versions by entity');

  Version.find({'entity.entityId': entityId})
          .exec(callback);
}

但是,当我拨打完全相同的电话但使用socket.io时,observable不返回任何内容。我猜是因为它永远不会完成?成功传输数据后,http呼叫会发送“完成”消息吗?

套接字是通过此服务发送的:

getVersionsByEntity(entityId): Observable<IVersion[]> {
    // create observable to list to refreshJobs message
    let observable = new Observable(observer => {
      this._socketService.socket.on('versionsByEntity', (data) => {
        observer.next(data);
      });
    });

    this._socketService.event('versionsByEntity', entityId);

    return <Observable<IVersion[]>> observable;
  }

从服务器调用相同的猫鼬函数。

从(套接字)服务返回的可观察对象实际上确实包含数据,仅当我添加了toArray()函数后,它在我订阅时就不会打印任何内容。

有人可以帮我解决这个问题,还可以解释其背后的理论吗?还没完成吗? http是否通过Angular发送“已完成”消息?

编辑:我已经创建了一个简单的stackblitz,它可以实现我想要的功能,但是我想从_dataService中删除take(1),因为我可能想更新数据,所以我想保持可观察的打开状态-{{ 3}}

编辑2:我已经关闭了,用扫描运算符替换了toArray,但是它似乎为数组发射了两次。 reduce()发出正确的数据,但似乎仅在完整时发出(如toArray),因此没有更好的效果-https://stackblitz.com/edit/rxjs-toarray-problem

2 个答案:

答案 0 :(得分:1)

处理事件流有些不同。我试图在下面提出一些代码:

getVersionsByEntity(entityId): Observable<IVersion[]> {
    return defer(() => {
        this._socketService.event('versionsByEntity', entityId);

        return fromEvent(this._socketService.socket, 'versionsByEntity')
            .pipe(take(1))
    }) as Observable<IVersion[]>;
}

主要思想是将所有内容包装在defer中,这将使Observable变得懒惰,并且仅在订阅时才调用socketService.event(您的原始实现非常渴望通过立即调用socketService.event)。渴望实现可能会产生意想不到的后果-如果Observable订阅得太晚,很容易错过事件。

我还建议使用fromEvent可观察的工厂-处理事件侦听器的设置和拆除。

最后我要完成第一次可观测值,我添加了take(1)-这将把发射数量限制为1,并取消订阅可观测值。

答案 1 :(得分:1)

除了@ m1ch4ls所说的以外,您还必须考虑toArray的工作方式。 toArray将Observable通知的所有数据转换为此类数据的数组。为了使它起作用,必须完成Observable。

由Angular http客户端返回的Observable总是在第一个通知之后完成,因此toArray起作用。一个socket.io流在关闭时完成,因此在不关闭流的情况下,在此类流上使用toArray可能永远不会从Observable中获取任何值。

另一方面,如果您想在一个通知后关闭流,这是使用take(1)时发生的情况,那么最好考虑保留http请求。套接字流被认为是存在时间很长的通道,因此,如果您必须在发送完一条消息后始终关闭它们,就不适合它们的性质。

评论后的更新版本

这是没有take

的代码
 getData() {
    return this._dataService.getVersions().pipe(
      map(
        (versions: Array<any>) => versions.reduce(
          (acc, val) => {
            const versionGroup = (acc.find(el => el.type === val.type));
            if (versionGroup) {
              versionGroup.versions.push(val)
            } else {
              acc.push({type: val.type, versions: [val]})
            }
            return acc;
          }, []
        )
      ),
    )

要理解的关键是您的服务正在向您返回Array的事物。为了获得您的结果,您可以使用Array方法直接在该Array上进行操作,而无需使用Observable运算符。请不要认为我上面使用的分组逻辑代码是正确的实现-真正的实现可能会受益于使用lodash之类的分组功能,但是我不想过分复杂。

这是您的原始代码

getData() {
    return this._dataService.getVersions().pipe(
      mergeMap((versions: Array<any>) => versions),
      groupBy((version:IVersion) => version.type),
      mergeMap(group => group.pipe(
        toArray(),
        map(versions=> {
          return {
            type: group.key,
            versions: versions
          }
        }),
        toArray()
      )),
      reduce((acc, v) => acc.concat(v), [])
    )
  }

这是什么

  1. 您创建了一个对象流,将第一个mergeMap应用于 服务返回的数组
  2. 通过groupBy运算符,您创建了一个新的Observable,它发出了

  3. 根据逻辑将对象分组,然后再输入第二个 复杂的mergeMap,它接收groupBy发出的数组, 将它们变成对象流,只是立即将它们转换 通过第一个toArray再次返回一个数组,然后 转换为{type:字符串,版本:[]}类型的Object 然后最终再次调用toArray以创建一个数组Array

  4. 您要做的最后一件事是运行reduce创建最终的数组

为什么它仅与take一起使用?由于groupBy仅在其源Observable的源代码完成时才执行,因此您希望对一组有限的事物进行分组很有意义。 reduce可观察的运算符也是如此。

take是完成可观察源的一种方法。