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:

SwitchMap stworzony przez ReactiveX na licencji CC-3 \(http://reactivex.io/documentation/operators/flatmap.html\)

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: