基于您的简化场景,我构建了一个工作示例,但有趣的是理解发生了什么.
首先,我构建了一个服务来模拟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
方法返回一个新实例,则将有两个不同的观察值.无论你是否使用共享.所以这里的 idea 是"准备"请求.CF myDataRes$
.这只是请求,然后是share
.但它只声明一次,并从getSomeData
方法返回引用.
现在,如果您从两个不同的组件订阅observable(服务调用的结果),您的控制台中将有以下内容:
Calling the service for the 1 time
Request n°1 - URL "some-url"
Calling the service for the 2 time
正如你所看到的,我们有两个呼叫服务,但只有一个请求.
Yeah!
如果你想确保一切按预期进行,只需用.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"
But... It's far from ideal.个
delay
%的模拟服务可以模拟网络延迟.But also hiding a potential bug
从stackblitz复制,转到组件second
并取消对setTimeout的注释.它会在1秒后呼叫服务.
我们注意到,现在,即使我们使用服务中的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请求只返回一个值一样,这个值也是因为我们使用的是of
).
但share
只不过是publish
和refCount
.Publish允许我们多播结果,refCount允许我们在没有人监听可观察内容时关闭订阅.
So with your 100, if one of your component is created later than it takes to make the first request, you'll still have another request.
为了避免这种情况,我想不出任何明智的解决方案.使用多播,我们必须使用连接方法,但具体在哪里?做一个条件和一个计数器来知道这是否是第一个电话?感觉不对.
所以这可能不是最好的主意,如果有人能提供更好的解决方案,我会很高兴,但与此同时,我们可以做些什么来保持可观察的"活着":
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