Ulepszanie wyszukiwania za pomocą switchMap
Występuje problem z naszą wcześniejszą implementacją wyszukiwania przyrostowego.
Co się stanie, jeśli z jakiegoś powodu serwer potrzebuje bardzo dużo czasu, aby odpowiedzieć na konkretne zapytanie? Jeśli użyjemy flatMap
, ryzykujemy odzyskanie wyników z serwera w niewłaściwej kolejności. Zilustrujmy to przykładem.
Przykład:
Rozważmy sytuację, w której najpierw wpisujemy litery ABC
, i przypuśćmy, że ciąg ABC
jest w rzeczywistości specjalnym ciągiem, na który odpowiedź zajmuje kilka sekund.
W międzyczasie, po tym, jak wstrzymaliśmy się na chwilę (więcej niż czas odbicia - debounceTime), decydujemy się na wpisanie kolejnej litery (litera X), a nasza aplikacja wysyła do serwera zapytanie o ciąg ABCX
. Ponieważ ABCX
nie jest uważany za specjalny ciąg, serwer odpowiada bardzo szybko, a nasza aplikacja ustawia sugestie dlaABCX
.
Kilka sekund później serwer ostatecznie odpowiada odpowiedzią na ciąg ABC
, a nasza aplikacja odbiera tę odpowiedź i ustawia sugestie wyszukiwania dlaABC
, nadpisując sugestie dla ciągu ABCX
, nawet jeśli prośba o to faktycznie przyszła później.
Oto prosty diagram ilustrujący problem:
// A1: Request for `ABC`
// A2: Response for `ABC`
// B1: Request for `ABCX`
// B2: Response for `ABCX`
--A1----------A2-->
------B1--B2------>
Możesz zobaczyć, że A2 przybywa po B2, mimo że żądanie A1 zaczęło się jako pierwsze. To zakończy się wyświetleniem błędnych wyników dla użytkownika. Jeśli ostatnim wprowadzeniem w wyszukiwaniu było ABCX, dlaczego widzę wyniki dlaABC? Aby obejść ten problem, musimy zamienić flatMap naswitchMap.
Co to jest switchMap
?
switchMap
jest bardzo podobny doflatMap
, ale z bardzo ważnym rozróżnieniem. Wszystkie zdarzenia, które mają zostać połączone w strumieniu, są ignorowane, jeśli pojawi się nowe zdarzenie. Oto diagram pokazujący zachowanie switchMap
:
Krótko mówiąc, za każdym razem, gdy zdarzenie zostanie wywołane przez strumień, flatMap
zasubskrybuje (i wywoła) nowe obserwowalne bez wypisywania się z jakiegokolwiek innego obserwowalnego obiektu utworzonego przez poprzednie zdarzenie. switchMap
z drugiej strony automatycznie zrezygnuje z subskrypcji z poprzednich obserwowanych zdarzeń, gdy nowe zdarzenie pojawi się w strumieniu.
Na powyższym diagramie okrągłe kulki
reprezentują zdarzenia w strumieniu początkowym. W wynikowym strumieniu diamenty
oznaczają tworzenie (i subskrypcję) obserwowalnego obiektu wewnętrznego (który jest ostatecznie połączony ze strumieniem pnia), a kwadraty
reprezentują wartości emitowane z tego samego obserwowalnego obiektu wewnętrznego.
Podobnie jak flatMap
, czerwony marmur zostaje zastąpiony czerwonym rombem, a następnie czerwonym kwadratem. Interakcja pomiędzy wydarzeniami z zielonego i niebieskiego marmuru jest bardziej interesująca. Zauważ, że zielony marmur jest natychmiast mapowany na zielony diament. A jeśli minie wystarczająco dużo czasu, zielony plac zostanie wepchnięty w strumień, ale tego tutaj nie widzimy.
Zanim zdarzy się wydarzenie w zielonym kwadracie, niebieski marmur przejdzie do niebieskiego diamentu. Stało się tak, że zielony kwadrat jest teraz ignorowany i nie wraca z powrotem do strumienia pnia. Zachowanie switchMap
można porównać doflatMap
, która przełącza się
na bardziej bezpośrednie zdarzenie przychodzące i ignoruje wszystkie wcześniej utworzone strumienie zdarzeń.
W naszym przypadku, ponieważ wydarzenie z niebieskiego marmuru nastąpiło bardzo szybko po zielonym marmurze, przełączaliśmy się
, aby skupić się na radzeniu sobie z niebieskim marmurem zamiast tego. Takie zachowanie zapobiega problemowi opisanemu powyżej.
Jeśli zastosujemy switchMap
do powyższego przykładu, odpowiedź dlaABC
zostanie zignorowana i pozostaną sugestie dla ABCX
.
Rozszerzone wyszukiwanie z switchMap
Oto skorygowany komponent używający switchMap
zamiastflatMap
.
app/app.component.ts
import { Component } from '@angular/core';
import { FormControl,
FormGroup,
FormBuilder } from '@angular/forms';
import { SearchService } from './services/search.service';
import 'rxjs/Rx';
@Component({
selector: 'app-root',
template: `
<form [formGroup]="coolForm"><input formControlName="search"
placeholder="Search book"></form>
<div *ngFor="let book of result">
{{book.title}} [{{book.authors}}]
</div>
`
})
export class AppComponent {
searchField: FormControl;
coolForm: FormGroup;
result : any;
constructor(private searchService:SearchService, private fb:FormBuilder) {
this.searchField = new FormControl();
this.coolForm = fb.group({search: this.searchField});
this.searchField.valueChanges
.debounceTime(400)
.switchMap(term => this.searchService.search(term))
.subscribe(result => {
this.result = result;
});
}
}
Zobacz przykład [069]
Ta implementacja wyszukiwania przyrostowego z switchMap
jest bardziej niezawodna niż ta, którą widzieliśmy na poprzedniej stronie zflatMap
. Sugestie, które widzi użytkownik, zawsze ostatecznie odzwierciedlają ostatnią rzecz, którą użytkownik wpisał. Dzięki temu możemy zagwarantować satysfakcję użytkownika bez względu na odpowiedź serwera.
Więcej informacji: