我觉得这是一个非常基本的问题,但我一直想知道我是否遗漏了一些基本的东西。这是场景。
每个客户(例如组件)只订阅一个可观察的
如果到目前为止还没有人请求该模型(例如,observable为空),则会自动触发该初始http请求。例如在所有组件被渲染/初始化之后,DOM被呈现给用户,如果observable为空,则自动发起http请求。 (请注意,我需要区分空observable和返回null对象的http调用)。这应该在任何路线导航或“页面加载”上重复(Angular世界中非常含糊的术语,但你知道我的意思)。
如果webockets请求带有该版本的新版本,请更新相同的observable,因此客户端不关心它是来自初始http请求还是websockets请求
如果延迟加载不是问题(例如,如果我没问题,模型将始终按概念“页面加载”加载一次),那么我想我可以做这样的事情
//totally made RoutingLifeCycle up, but I assume Angular has something like it
@Injectable()
class ModelLoadingService implements RoutingLifeCycle {
/**
/* only keep the "last" copy of the model for future subscribers.
*/
private modelSubject = new ReplaySubject<Model>(1);
/**
/* the observable clients will subscribe to.
*/
model$ = this.modelSubject.asObservable()
/**
/* Right before every conceptual "page load", where (except perhaps
/* the app component) many components get either destroyed or
/* initialized in a short time.
/* Again, I totally made this "onBeforeRouteChanged" method up but
/* I assume Angular has something like this
*/
onBeforeRouteChanged() {
//problem is that if there are no subscribers for it in this "page"
// then I wasted an http request. this is not "lazy"
// Side question... IS THERE A BETTER WAY THAN THIS?
// I can't pass an Observable<Model> to the Subject.next method
// And I can't .startWith as the subject is kind of immutable,
// And I can't "replace" model$ as the subscribers subscribed
// To the old one, (is there a way to "move the subscribers" to
// A new observable?) I feel I'm taking crazy pills
getModel().subscribe(model => this.modelSubject.next(model));
}
/**
* loads a model via a regular http request
*/
getModel(): Observable<Model> {
return http.get(...);
}
/**
* receives a model from a websockets message and multicasts it
*/
onWebsocketsMessage(message:string) {
// omitted error handling etc...
this.modelSubject.next(JSON.parse(message) as Model);
}
}
如果我想避免关心页面重新加载触发器,我想我可以使用像shareReplay(1, 1000)
这样的东西(还添加处理上面例子中未处理的“get by ID”情况)
getModelById(id:string):Observable<Model> {
let modelObservable = this.modelMap.get(id);
if(!modelObservable) {
model = http.get(...).shareReplay(1, 1000);
this.modelMap.set(id, modelObservable);
}
return modelObservable;
}
然后我使用websockets主题merge
,并且只公开那个唯一可观察的客户端可以用于该项目。这是正确的方法吗?
用Rx实现类似行为的惯用方法是什么?我是朝着正确的方向吗?或者还有很长的路要爬山。
上面我的问题怎么样? :)将Observable<Model>
传递给Subject.next
的好方法是什么? (observable.subscribe(model => subject.next(model));
除外)
答案 0 :(得分:1)
你走在正确的轨道上。
您需要采取的步骤是:
答案 1 :(得分:1)
我没有纯粹的Rx解决方案,但我想分享一些从各种方面解决这个问题的想法。
Redux(命令查询分离)
我同意@dee zg,命令查询分离原则使得对数据流的推理更加容易。试图找到一个纯粹的Rx解决方案似乎类似于只用一个扳手来为您的汽车服务。我首先通过“缓存”解决了这个问题。服务,但结果是复杂和脆弱的。转移到Redux,解决方案感觉好多了。
处理状态
Ben Richards解释的类似单身模式是避免多个请求的关键,但它不仅仅是案例模型空/模型已满。问题是HTTP请求需要花费时间,如果第二个客户端在第一个客户端加载时订阅,则会出现重叠
可能吗,这是一个问题吗?取决于应用程序,但如果确实发生,可能很难调试。
已排序的请求
另一方面是可能存在所需的一系列资源,并且对于每个组件,顺序可以是不同的,如同多个请求的排队&#39;你引用的问题。
我认为序列应该由组件拥有并在组件中定义。使用工资单应用程序显示任何人都可以查看的摘要页面,并详细说明需要登录令牌才能访问的页面。两者都使用数据获取服务,但只有详细信息页面需要获取令牌 - 因此令牌提取不能与数据服务绑定。
我目前的结构
组件调用一系列命令&#39;关于init的服务。该命令仅返回信号完成的承诺,以允许组件排序并等待资源。
initialize () {
configService.checkConfig()
.then(_ => {
dataService.checkFiles(this.page)
})
}
服务定义命令,但不动作 - 它们将其与处理提取,失败和超时的方法一起交给商店操作。
checkConfig () {
return store.dispatch('waitForFetch', {
resource: 'config',
fetch: this.getConfig // this method defines the request
})
}
Store操作处理每个资源的状态,类似于modelMap对象,但存储在Redux状态分支中,因此可以在devtools中轻松查看。
const actions = {
waitForFetch: (context, {resource, fetch, fail, timeout}) => {
return
isLoaded(resource) ? Promise.resolve()
: isInitial(resource) ? doFetch(resource, fetch, fail)
: isLoading(resource) ? doWaitForLoading(resource, timeout)
: null
}
}
它并不完美,它不是Rx(订阅商店除外),但它灵活,易懂,可测试。