Скажем, у одного есть API, который принимает запросы и возвращает потоки результатов, так как некоторые результаты могут измениться.
type Query = {
species?: "dog" | "cat" | "rat",
name?: "string",
status?: "lost" | "found"
}
type Result = { species: string, name: string, status: string }[]
Скажем, есть несколько компонентов, передающих запросы к этому API, и некоторые из них могут быть одинаковыми. Один не хочет посылать ненужные запросы на сервер и любит оптимизировать - для этого этот API может быть обернут и вызовы перехвачены.
interface ServiceApi {
request(query: Query): Observable<Result>
}
class WrappedServiceApi implements ServiceApi {
constructor(private service: ServiceApi) { }
request(query: Query): Observable<Result> {
// intercepted
return this.service.request(query);
}
}
Но как подойти к такой оптимизации, используя RxJS 5?
Выполнение этого действия вокруг RxJS может выглядеть примерно так:
class WrappedServiceApi implements ServiceApi {
private activeQueries;
constructor(private service: ServiceApi) {
this.activeQueries = new Map<string, Observable<Result>>();
}
request(query: Query): Observable<Result> {
// it easy to stringify query
const hashed: string = hash(query);
if (this.activeQueries.has(hashed)) {
// reuse existing stream
return this.activeQueries.get(hashed);
} else {
// create multicast stream that remembers last value
const results = this.service.request(query).publishLast();
// store stream for reuse
this.activeQueries.set(hashed, results);
// delete stream 5s after it closed
results.toPromise().then(
() => setTimeout(
() => this.activeQueries.delete(hashed),
5000
)
);
return results;
}
}
}
Может ли это быть достигнуто и в более декларативном rx-way?
Я не тестировал его, но я бы сделал это следующим образом:
request(query: Query): Observable<Result> {
return Observable.of(hash(query))
.flatMap(hashed => {
const activeQueries = this.activeQueries;
if (activeQueries.has(hashed)) {
return activeQueries.get(hashed);
} else {
const obs = this.service.request(query)
.publishReplay(1, 5000)
.refCount()
.take(1);
activeQueries.set(hashed, obs);
return obs;
}
});
}
В основном единственное различие заключается в том, что я this.activeQueries
Observables в this.activeQueries
а не только их результаты, а затем я .publishReplay(1, 5000)
их на 5 с использованием .publishReplay(1, 5000)
. Таким образом, когда вы подписываетесь на тот же Observable после 5s, он будет просто повторно подписываться на свой источник Observable.