目前有一个场景,其中共享服务中的一个方法由多个组件使用。此方法对endpoint进行HTTP调用,该endpoint将始终具有相同的响应,并返回一个可观察值。是否可以与所有订阅者共享第一个响应以防止重复HTTP请求?
以下是上述方案的简化版本:
class SharedService {
constructor(private http: HttpClient) {}
getSomeData(): Observable<any> {
return this.http.get<any>('some/endpoint');
}
}
class Component1 {
constructor(private sharedService: SharedService) {
this.sharedService.getSomeData().subscribe(
() => console.log('do something...')
);
}
}
class Component2 {
constructor(private sharedService: SharedService) {
this.sharedService.getSomeData().subscribe(
() => console.log('do something different...')
);
}
}
即使其他人在工作前提出的解决方案,我发现必须手动为每个不同的get/post/put/删除
请求在每个类中创建字段很烦人。
我的解决方案基本上基于两个想法:一个是管理所有http请求的HttpService
,另一个是管理实际通过哪些请求的PendingService
。
这样做的目的不是拦截请求本身(我可以使用HttpInterceptor
,但是已经太晚了,因为已经创建了请求的不同实例),而是在发出请求之前拦截请求的意图。
所以基本上,所有请求都通过这个PendingService
,它保存了一个Set
挂起的请求。如果一个请求(由它的url标识)不在那个集合中,这意味着这个请求是新的,我们必须调用HttpClient
方法(通过回调),并将其保存为我们集合中的挂起请求,将其url作为键,并且请求可观察为值。
如果稍后对同一个url有请求,我们使用它的url再次检查集合,如果它是我们挂起的集合的一部分,这意味着...挂起,所以我们简单地返回我们之前保存的可观察的。
每当挂起的请求完成时,我们调用一个方法将其从集合中删除。
下面是一个例子,假设我们请求。。。我不知道,吉娃娃?
这将是我们的小型吉娃娃服务
:
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpService } from '_services/http.service';
@Injectable({
providedIn: 'root'
})
export class ChihuahuasService {
private chihuahuas: Chihuahua[];
constructor(private httpService: HttpService) {
}
public getChihuahuas(): Observable<Chihuahua[]> {
return this.httpService.get('https://api.dogs.com/chihuahuas');
}
public postChihuahua(chihuahua: Chihuahua): Observable<Chihuahua> {
return this.httpService.post('https://api.dogs.com/chihuahuas', chihuahua);
}
}
类似这样的东西将是HttpService
:
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { share } from 'rxjs/internal/operators';
import { PendingService } from 'pending.service';
@Injectable({
providedIn: 'root'
})
export class HttpService {
constructor(private pendingService: PendingService,
private http: HttpClient) {
}
public get(url: string, options): Observable<any> {
return this.pendingService.intercept(url, this.http.get(url, options).pipe(share()));
}
public post(url: string, body: any, options): Observable<any> {
return this.pendingService.intercept(url, this.http.post(url, body, options)).pipe(share());
}
public put(url: string, body: any, options): Observable<any> {
return this.pendingService.intercept(url, this.http.put(url, body, options)).pipe(share());
}
public delete(url: string, options): Observable<any> {
return this.pendingService.intercept(url, this.http.delete(url, options)).pipe(share());
}
}
最后是PendingService
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/internal/operators';
@Injectable()
export class PendingService {
private pending = new Map<string, Observable<any>>();
public intercept(url: string, request): Observable<any> {
const pendingRequestObservable = this.pending.get(url);
return pendingRequestObservable ? pendingRequestObservable : this.sendRequest(url, request);
}
public sendRequest(url, request): Observable<any> {
this.pending.set(url, request);
return request.pipe(tap(() => {
this.pending.delete(url);
}));
}
}
这样,即使6个不同的组件调用ChihuahasService.getChihuahuas()
,实际上也只会发出一个请求,我们的dogs API也不会抱怨。
我相信它可以改进(我欢迎建设性的反馈)。希望有人觉得这有用。
基于您的简化场景,我构建了一个工作示例,但有趣的是了解发生了什么。
首先,我构建了一个服务来模拟HTTP,避免进行真正的HTTP调用:
export interface SomeData {
some: {
data: boolean;
};
}
@Injectable()
export class HttpClientMockService {
private cpt = 1;
constructor() {}
get<T>(url: string): Observable<T> {
return of({
some: {
data: true,
},
}).pipe(
tap(() => console.log(`Request n°${this.cpt++} - URL "${url}"`)),
// simulate a network delay
delay(500)
) as any;
}
}
进入AppModule
我已经替换了真实的HttpClient以使用模拟的HttpClient:
{ provide: HttpClient, useClass: HttpClientMockService }
现在,共享服务:
@Injectable()
export class SharedService {
private cpt = 1;
public myDataRes$: Observable<SomeData> = this.http
.get<SomeData>("some-url")
.pipe(share());
constructor(private http: HttpClient) {}
getSomeData(): Observable<SomeData> {
console.log(`Calling the service for the ${this.cpt++} time`);
return this.myDataRes$;
}
}
如果从getSomeData
方法返回一个新实例,则将有两个不同的可观察对象。无论您是否使用共享。因此,这里的想法是“准备”请求。CFmyDataRes$
。它只是一个请求,然后是一个共享
。但是它只声明一次,并从
getSomeData
方法返回该引用。
现在,如果您从两个不同的组件订阅可观察的(服务调用的结果),您的控制台中将有以下内容:
Calling the service for the 1 time
Request n°1 - URL "some-url"
Calling the service for the 2 time
正如您所看到的,我们有2次呼叫该服务,但只提出了一个请求。
是 啊
如果您想确保一切正常工作,只需使用
.pipe(share())
注释掉该行即可:
Calling the service for the 1 time
Request n°1 - URL "some-url"
Calling the service for the 2 time
Request n°2 - URL "some-url"
但是这远非理想。
模拟服务中的
延迟
对于模拟网络延迟是很酷的。但也隐藏了一个潜在的bug。
从stackblitz复制,转到组件
second
并取消对设置超时的注释。它将在1s后呼叫服务。
我们注意到,现在,即使我们使用服务中的
share
,我们也有以下几点:
Calling the service for the 1 time
Request n°1 - URL "some-url"
Calling the service for the 2 time
Request n°2 - URL "some-url"
为什么?因为当第一个组件订阅可观察对象时,由于延迟(或网络延迟),500毫秒内不会发生任何事情。因此,订阅在此期间仍然有效。一旦500毫秒延迟完成,可观察的就完成了(它不是一个长寿命的可观察的,就像HTTP请求只返回一个值一样,这个值也是因为我们使用的是
的)。
但是
share
只不过是一个发布
和reCount
。Publish允许我们多播结果,而reCount允许我们在没有人监听可观察到时关闭订阅。
因此,对于使用共享的解决方案,如果其中一个组件的创建时间晚于发出第一个请求所需的时间,那么您仍然会有另一个请求。
为了避免这种情况,我想不出任何好的解决办法。使用多播,我们必须使用connect方法,但具体在哪里?做一个条件和一个计数器来知道这是否是第一次呼叫?感觉不对。
因此,这可能不是最好的主意,如果有人能提供更好的解决方案,我会很高兴,但与此同时,我们可以做些什么来保持可观察的“活着”:
private infiniteStream$: Observable<any> = new Subject<void>().asObservable();
public myDataRes$: Observable<SomeData> = merge(
this
.http
.get<SomeData>('some-url'),
this.infiniteStream$
).pipe(shareReplay(1))
由于infiniteStream$从未关闭,我们正在合并这两个结果,并使用
shareReplay(1)
,我们现在得到了预期结果:
一个HTTP调用,即使对服务进行了多个调用。不管第一个请求需要多长时间。
这里有一个Stackblitz演示来说明所有这些:https://stackblitz.com/edit/angular-n9tvx7
在尝试了一些不同的方法后,遇到了这个方法,它解决了我的问题,并且无论有多少订阅者,都只发出一个HTTP请求:
class SharedService {
someDataObservable: Observable<any>;
constructor(private http: HttpClient) {}
getSomeData(): Observable<any> {
if (this.someDataObservable) {
return this.someDataObservable;
} else {
this.someDataObservable = this.http.get<any>('some/endpoint').pipe(share());
return this.someDataObservable;
}
}
}
我仍然愿意接受更有效的建议!
给好奇的人:share()
本文向大家介绍防止重复发送 Ajax 请求,包括了防止重复发送 Ajax 请求的使用技巧和注意事项,需要的朋友参考一下 要考虑并理解 success, complete, error, timeout 这些事件的区别,并注册正确的事件,一旦失误,功能将不再可用; 不可避免地比普通流程要要多注册一个 complete 事件; 恢复状态的代码很容易和不相干的代码混合在一起; 推荐用主动查询状态的方式(
问题内容: 我想创建一个表来存储设备设置。该表具有三行:id,parameter_name和parameter_value。 该表是通过执行以下查询语句创建的: 然后通过执行以下方法存储行: 创建数据库后,将存储默认值: 但是,方法insertRow()的问题在于它无法防止重复输入。 有谁知道在这种情况下如何防止重复输入? 问题答案: 您可以使用列约束。 UNIQUE约束导致在指定列上创建唯一索引
本文向大家介绍Spring Boot如何防止重复提交,包括了Spring Boot如何防止重复提交的使用技巧和注意事项,需要的朋友参考一下 场景:同一个用户在2秒内对同一URL的提交视为重复提交。 思考逻辑: 1.从数据库方面考虑,数据设计的时候,某些数据有没有唯一性,如果有唯一性,要考虑设置唯一索引,可以避免脏数据。 2.从应用层面考虑,首先判断是单机服务还是分布式服务,则此时需要考虑一些缓存,
这是我的用户注册数据库的方式,但我的问题是:如何防止数据库具有相同用户名的副本,或者换句话说,如果用户名存在于数据库。
问题内容: 我不想拥有用户或位置,因为我可以有多行用户包含相同数据,或者有多行位置包含相同数据。我只想避免用户和位置都具有一定的价值,因为该行重复了许多次。 例如:这还可以 但这不行: 因为已经存在其中user = 1和location = 2的行。 如何避免重复? 问题答案: 声明对(用户,位置)的唯一约束。
1、通过JavaScript屏蔽提交按钮(不推荐) 2、给数据库增加唯一键约束(简单粗暴) 3、利用Session防止表单重复提交(推荐) 4、使用AOP自定义切入实现