我有一个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
答案 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), [])
)
}
这是什么
mergeMap
应用于
服务返回的数组通过groupBy
运算符,您创建了一个新的Observable,它发出了
根据逻辑将对象分组,然后再输入第二个
复杂的mergeMap
,它接收groupBy
发出的数组,
将它们变成对象流,只是立即将它们转换
通过第一个toArray
再次返回一个数组,然后
转换为{type:字符串,版本:[]}类型的Object
然后最终再次调用toArray
以创建一个数组Array
reduce
创建最终的数组为什么它仅与take
一起使用?由于groupBy
仅在其源Observable的源代码完成时才执行,因此您希望对一组有限的事物进行分组很有意义。 reduce
可观察的运算符也是如此。
take
是完成可观察源的一种方法。