Logo

Rozsynchronizowanie inputa z listą pochodzącą z api

24 February 2020

Wstęp

Wykonując zapytania do api na podstawie inputu tekstowego użytkownika łatwo doprowadzić do sytuacji, w której rozsynchronizujemy wyświetlaną listę oraz input. W tym artykule przedstawię z czego to może wynikać oraz jakie rozwiązania możemy zastosować.

Problem

Wybraźmy sobie następującą sytuację. Mamy na stronie input, który pozwala na na filtrowanie listy. Ze względu na to, że lista jest duża, nie chcemy ściągać jej całej tylko przy każdej zmianie wykonujemy zapytanie do api.

Jeżeli przy każdej zmianie będziemy wykonywać zapytanie łatwo o sytuację, gdzie nasz tekst w inpucie będzie rozsynchronizowany względem otrzymanej odpowiedzi.

Załóżmy, że użytkownik wpisał "jozek". Do api poleci 5 zapytań:

  1. api/?name=j
  2. api/?name=jo
  3. api/?name=joz
  4. api/?name=joze
  5. api/?name=jozek

Niestety nic nie gwarantuje nam, że otrzymamy odpowiedzi w tej samej kolejności! Ze względu na magię internetu odpowiedzi mogą wrócić w nastepującej kolejności:

  1. api/?name=j
  2. api/?name=joz
  3. api/?name=jo
  4. api/?name=jozek
  5. api/?name=joze

W tej sytuacji, w naszym polu tekstowym będziemy mieli wpisane "jozek", natomiast lista będzie wyświetlać listę dla "joze".

Jakie mamy rozwiązania tej sytuacji?

Rozwiązania

Debounce

Debounce to technika polegająca na wykonaniu akcji dopiero gdy minie określony czas od ostatniej zmiany. Stosujemy go gdy nie chcemy wywoływać jakieś funkcji zbyt często.

Przykładowo, jeśli nasz użytkownik wpisuje kolejny znak co 200ms, a my ustawimy czas oczekiwania na 500ms zamiast wykonać 5 zapytań wykonamy tylko 1. Modyfikując powyższy przykład:

  1. (0ms) wartość inputa =j
  2. (200ms) wartość inputa =jo
  3. (400ms) wartość inputa =joz
  4. (600ms) wartość inputa =joze
  5. (800ms) wartość inputa =jozek
  6. (1300ms)zapytanie do api/?name=jozek

Zatem zamiast wykonać 5 zapytań wykonamy tylko jedno dopiero gdy użytkownik skończy pisać.

Poniżej przykładowa implementacja. Możesz też skorzystać z lodasha

const debounce = (callback, time) => {
  let timer;

  return () => {
    // jeśli funkcja nie zdążyła się wywołać to usuń timer
    clearTimeout(timer);

    // ustaw by po określonym czasie funkcja się wywołała
    timer = setTimeout(callback, time);
  };
};

Rozwiązanie to ma jednak parę wad:

  • użytkownicy mają różne tempo pisania, dla niektórych to nic nie da bo i tak piszą wolno natomiast dla reszty strona bedzie wydawała się mniej responsywna(będzie wolniej reagować)
  • musimy próbować odgadnać jaki będzie najrozsądniejszy balans między szybkością (chcemy jak najszybciej odpytać api) oraz stabilnością (chcemy mieć dość czasu by uniknąć problemu z wyścigiem odpowiedzi z api)

Anulowanie wcześniejszych zapytań

Jeżeli korzystamy z biblioteki wspierającej anulowanie zapytań możemy wykorzystać tę funkcjonalność. Przykładowa implementacja poniżej, bazujac na odpowiedzi z issue na githubie.

export const createCancellableGetRequest = () => {
  // tu będziemy trzymać referencję do poprzedniego zapytania (oraz tokenu do anulowania)
  let call;
  return function(url) {
    // jak mamy jakiś token to ubijmy wcześniejsze zapytanie
    if (call) {
      call.cancel();
    }
    // zapisz token
    call = axios.CancelToken.source();
    // wykonaj zapytanie
    return axiosInstance.get(url, { cancelToken: call.token });
  };
};

const get = createCancellableGetRequest();
get('url').then();

Co ważne musimy potem obsłużyć dwie rzeczy:

  1. Anulowane zapytania będą zwracały błędy. Możemy to obsłużyć na poziomie interceptora
axiosInstance.interceptors.response.use(
  response => {
    return response;
  },
  error => {
    if (axios.isCancel(error)) {
      // błąd wyrzucany przy ubiciu zapytania
      return;
    }
  1. W wyniku powyższego nasze anulowane zapytania będą zwracać pusty response, co musimy obsłużyć by nie było typeerrorów!

Skorzystanie z observabli

Niestety nie mam jakiegoś sensownego przykładu pod ręką więc wrzucam pierwszy lepszy artykuł z neta

W skrócie zamiast reagować na zmiany tworzymy na nie subskrypcje. W momencie wystąpienia zmiany tworzone jest nowe zapytanie a wcześniejsze są ignorowane (lub anulowane).


Józef Piecyk

Napisane przez Józef Piecyk psiate, tate i fana sucharów.