根据您的简化场景,我构建了一个工作示例,但有趣的部分是了解发生了什么。
首先,我构建了一个服务来模拟 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来使用模拟的:
{ 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 方法返回一个新实例,您将拥有 2 个不同的可观察对象。无论您是否使用共享。所以这里的想法是“准备”请求。 CFmyDataRes$。这只是请求,然后是share。但它只声明一次并从 getSomeData 方法返回该引用。
现在,如果您从 2 个不同的组件订阅 observable(服务调用的结果),您将在控制台中看到以下内容:
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"
但是......这远非理想。
模拟服务中的delay 很酷,可以模拟网络延迟。 但也隐藏了一个潜在的错误。
从 stackblitz repro 中,转到组件 second 并取消注释 setTimeout。它会在 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"
为什么?因为当第一个组件订阅 observable 时,由于延迟(或网络延迟),在 500 毫秒内什么都没有发生。所以订阅在那段时间仍然有效。一旦 500ms 延迟完成,observable 就完成了(它不是一个长期存在的 observable,就像一个 HTTP 请求只返回一个值,这个也是因为我们使用的是of)。
但share 只不过是publish 和refCount。 Publish 允许我们对结果进行多播,而 refCount 允许我们在没有人监听 observable 时关闭订阅。
因此,对于您的 solution using share,如果您的某个组件的创建时间晚于发出第一个请求所需的时间,您仍然会有另一个请求。
为避免这种情况,我想不出任何出色的解决方案。使用多播我们必须使用 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