通过使用Http,我们调用一个执行网络调用并返回http observable的方法:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
如果我们采用这个可观察的并添加多个订阅者:
let network$ = getCustomer();
let subscriber1 = network$.subscribe(...);
let subscriber2 = network$.subscribe(...);
我们想要做的是确保这不会导致多个网络请求 .
这可能看起来像是一个不寻常的场景,但它实际上很常见:例如,如果调用者订阅observable以显示错误消息,并使用异步管道将其传递给模板,我们已经有两个订阅者 .
在RxJs 5中这样做的正确方法是什么?
即,这似乎工作正常:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json()).share();
}
但这是在RxJs 5中这样做的惯用方式,还是我们应该做其他事情呢?
注意:根据Angular 5 new HttpClient
,所有示例中的 .map(res => res.json())
部分现在都是无用的,因为现在默认采用JSON结果 .
20 回答
根据@Cristian的建议,这是一种适用于HTTP observable的方法,它只发出一次然后完成:
它是
.publishReplay(1).refCount();
或.publishLast().refCount();
,因为Angular Http observables在请求后完成 .这个简单的类缓存结果,因此您可以多次订阅.value并只发出1个请求 . 您还可以使用.reload()发出新请求并发布数据 .
您可以像以下一样使用它:
和来源:
rxjs 5.3.0
我对
.map(myFunction).publishReplay(1).refCount()
不满意对于多个订阅者,
.map()
在某些情况下执行两次myFunction
(我希望它只执行一次) . 一个修复似乎是publishReplay(1).refCount().take(1)
你可以做的另一件事是,不要使用
refCount()
并立即让Observable变热:无论订户如何,这都将启动HTTP请求 . 我不确定在HTTP GET完成之前取消订阅是否会取消它 .
rxjs 5.4.0有一个新的 shareReplay 方法 .
rx-book shareReplay()
在reactivex.io/rxjs没有文档
作者明确说"ideal for handling things like caching AJAX results"
rxjs PR #2443 feat(shareReplay): adds shareReplay variant of publishReplay
我假设@ngx-cache/core可能有助于维护http调用的缓存功能,特别是如果在 browser 和 server 平台上进行HTTP调用 .
假设我们有以下方法:
您可以使用@ngx-cache/core的
Cached
装饰器来存储从cache storage
进行HTTP调用的方法返回的值(storage
可以配置,请检查ng-seed/universal处的实现) - 就在第一次执行时 . 下次调用该方法时(无论在 browser 或 server 平台上),都会从cache storage
中检索该值 .还可以使用caching API来使用缓存方法(
has
,get
,set
) .anyclass.ts
以下是客户端和服务器端缓存的软件包列表:
@ngx-cache/core:缓存实用程序
@ngx-cache/platform-browser:SPA /浏览器平台实施
@ngx-cache/platform-server:服务器平台实现
@ngx-cache/fs-storage:存储实用程序(服务器平台需要)
据此article
所以 inside if statements 只是追加
到
.map(...)
缓存数据,如果可用缓存,则返回此项,否则发出HTTP请求 .
Plunker example
这个artile https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html是一个很好的解释如何缓存
shareReplay
.使用Rxjs Observer / Observable Caching Subscription的可缓存HTTP响应数据
See Code Below
*免责声明:我是rxjs的新手,所以请记住,我可能会滥用可观察/观察者的方法 . 我的解决方案纯粹是我发现的其他解决方案的集合,是未能找到一个简单的记录良好的解决方案的结果 . 因此,我提供了完整的代码解决方案(正如我希望找到的那样),希望它可以帮助其他人 .
*请注意,此方法基于GoogleFirebaseObservables . 不幸的是,我缺乏适当的经验/时间来复制他们在引擎盖下做的事情 . 但以下是提供对某些可缓存数据的异步访问的简单方法 .
Situation :'product-list'组件的任务是显示产品列表 . 该网站是一个单页的Web应用程序,其中包含一些菜单按钮,这些按钮将显示在页面上的产品 .
Solution :组件"subscribes"为服务方法 . service方法返回一个产品对象数组,组件通过订阅回调访问该对象 . service方法将其活动包装在新创建的Observer中并返回观察者 . 在此观察者中,它搜索缓存的数据并将其传递回订阅者(组件)并返回 . 否则,它会发出http调用以检索数据,订阅响应,您可以在其中处理该数据(例如,将数据映射到您自己的模型),然后将数据传递回订阅者 .
The Code
产品list.component.ts
product.service.ts
product.ts(模型)
以下是我在Chrome中加载页面时看到的输出示例 . 请注意,在初始加载时,将从http(调用我的节点休息服务,在端口3000上本地运行)中提取产品 . 然后,当我单击以导航到产品的“已过滤”视图时,可以在缓存中找到产品 .
我的Chrome日志(控制台):
... [点击菜单按钮过滤产品] ...
结论:这是我发现(到目前为止)实现可缓存的http响应数据的最简单方法 . 在我的角度应用程序中,每次导航到产品的不同视图时,产品列表组件都会重新加载 . ProductService似乎是一个共享实例,因此ProductService中的“products:Product []”的本地缓存在导航期间保留,随后对“GetProducts()”的调用返回缓存的值 . 最后一点,我已经阅读了关于在完成防止“内存泄漏”时需要关闭observable / subscriptions的评论 . 我没有把它包含在这里,但要记住这一点 .
我主演了这个问题,但我会试着去试试这个问题 .
这是proof :)
只有一个外卖:
getCustomer().subscribe(customer$)
我们没有订阅
getCustomer()
的api响应,我们正在订阅一个可观察的ReplaySubject,它也可以订阅一个不同的Observable并且(这很重要)保持它's last emitted value and republish it to any of it'(ReplaySubject)的订阅者 .我写了一个缓存类,
它是静态的,因为我们如何使用它,但随意使它成为一个普通的类和服务 . 我不确定角度是否在整个时间内保持单个实例(Angular2新增) .
这就是我使用它的方式:
我假设可能有一种更聪明的方法,它会使用一些
Observable
技巧,但这对我的目的来说还不错 .您可以构建简单的类Cacheable <>,以帮助管理从多个订户的http服务器检索的数据:
Usage
声明Cacheable <>对象(可能是服务的一部分):
和处理程序:
从组件调用:
您可以订阅几个组件 .
更多细节和代码示例如下:http://devinstance.net/articles/20171021/rxjs-cacheable
很棒的答案 .
或者你可以这样做:
这是来自最新版本的rxjs . 我正在使用 5.5.7 版 RxJS
只需使用此缓存层,它就可以执行您需要的所有操作,甚至可以管理ajax请求的缓存 .
http://www.ravinderpayal.com/blogs/12Jan2017-Ajax-Cache-Mangement-Angular2-Service.html
这很容易使用
该层(作为可注入的角度服务)是
您是否尝试过运行已有的代码?
因为您正在根据
getJSON()
产生的承诺构建Observable,所以在任何人订阅之前都会发出网络请求 . 由此产生的承诺由所有订户共享 .只需在 map 之后和任何 subscribe 之前调用 share() .
在我的例子中,我有一个通用服务(RestClientService.ts),它正在进行其余的调用,提取数据,检查错误并将observable返回到具体的实现服务(f.ex . :ContractClientService.ts),最后这个具体实现将observable返回给de ContractComponent.ts,并且这个订阅更新视图 .
RestClientService.ts:
ContractService.ts:
ContractComponent.ts:
我找到了一种方法来将http get结果存储到sessionStorage并将其用于会话,以便它永远不会再次调用服务器 .
我用它来调用github API以避免使用限制 .
仅供参考,sessionStorage限制为5M(或4.75M) . 因此,它不应该像这样用于大量数据 .
------编辑-------------
如果你想用F5刷新数据,它使用内存数据而不是sessionStorage;
你可以简单地使用 ngx-cacheable !它更适合您的场景 .
那么,你的服务类就是这样的 -
Here 是更多参考的链接 .
我个人最喜欢的是对发出网络请求的呼叫使用
async
方法 . 方法本身不返回值,而是在同一服务中更新BehaviorSubject
,组件将是订阅 .现在为什么要使用
BehaviorSubject
而不是Observable
?因为,订阅时,BehaviorSubject返回最后一个值,而常规observable仅在收到
onnext
时触发 .如果要在不可观察的代码(没有订阅)中检索BehaviorSubject的最后一个值,可以使用
getValue()
方法 .Example:
customer.service.ts
然后,只要需要,我们就可以订阅
customers$
.或者您可能希望直接在模板中使用它
所以现在,在您再次调用
getCustomers
之前,数据将保留在customers$
BehaviorSubject中 .那么如果你想刷新这些数据怎么办?只需拨打
getCustomers()
使用这种方法,我们不会
BehaviorSubject
处理 .PS: 通常当一个组件被销毁时,摆脱订阅是一个好习惯,因为你可以使用this中建议的方法 .
更新:Ben Lesh说5.2.0之后的下一个小版本,你将能够只调用shareReplay()来真正缓存 .
先前.....
首先,不要使用share()或publishReplay(1).refCount(),它们是相同的,并且它的问题在于,只有在observable处于活动状态时进行连接才会共享,如果在完成后连接则进行连接,它再次创建一个新的可观察,翻译,而不是真正的缓存 .
Birowski在上面提供了正确的解决方案,即使用ReplaySubject . 在我们的案例1中,ReplaySubject将缓存你给它的值(bufferSize) . 一旦refCount达到零并且你 Build 一个新的连接,这将是缓存的正确行为,它不会创建像share()这样的新的observable .
这是一个可重用的功能
以下是如何使用它
下面是可缓存功能的更高级版本 . 这个允许自己的查找表能够提供自定义查找表 . 这样,您不必像上面的示例中那样检查this._cache . 另请注意,不是将observable作为第一个参数传递,而是传递一个返回observables的函数,这是因为Angular的Http立即执行,所以通过返回一个惰性执行函数,我们可以决定不调用它,如果它已经在我们的缓存 .
用法:
您选择的实现将取决于您是否希望unsubscribe()取消您的HTTP请求 .
无论如何,TypeScript decorators是标准化行为的好方法 . 这是我写的那个:
装饰者定义: