Задать вопрос
Fragster
@Fragster
помогло? отметь решением!

Как сделать код с отменой запросов и переотправкой более простым?

Есть вот такой код с отправкой запроса в апи по вводу в инпут и отменой отправки если данные изменились, а запрос ещё идет:

const citySuggest = ref()
const loadingCities = ref(false)
const citySuggestRef = useTemplateRef('citySuggestRef')
let cancelCityRequest = new AbortController
watch(() => delivery.value.city, debounce(async (val) => {
  let localSignal
  try {
    if (delivery.value.city === citySuggest.value) { return } // выбрали город из подсказки
    if (loadingCities.value) { // если еще загружем предыдущий запрос, то отменяем его
      cancelCityRequest.abort()
      cancelCityRequest = new AbortController //нужен новый контроллер, чтобы новый запрос не отменился сразу же
    }
    loadingCities.value = true
    localSignal = cancelCityRequest.signal // сохраняем локально, чтобы анализировать в finally, в глобальном объекте в это время будет новый контроллер
    const { data } = await api.get('suggest/city', { params: { q: val }, signal: localSignal })
    if (data && data !== delivery.value.city){ // если ввели название целиком, то показывать не надо
      citySuggest.value = data.value
      citySuggestRef.value.show(); // показываем список  с подсказкой
    }
  } catch (error) {
    console.error(error)
  } finally {
    if (!localSignal.aborted){
      loadingCities.value = false
    }
  }
}, 200))


и вот что-то мне прям не нравится его сложность и то, что, кажется, он может работать неверно, если тайминги сильно совпадут (индикатор загрузки не будет показываться)

Можно его как-то распутать и упростить?
  • Вопрос задан
  • 256 просмотров
Подписаться 1 Простой Комментировать
Пригласить эксперта
Ответы на вопрос 2
Aetae
@Aetae Куратор тега JavaScript
Тлен
Асинхронщина - это сложно. Асинхронщина с прерываниями в произвольном месте - сложно вдвойне.
"Прям красиво" в конечном коде не получится. Но можно сделать более-менее приличную абстракцию или воспользоваться готовой.
Например можно использовать @tanstack/vue-query - оно предусматривает большинство возможных сценариев. Из минусов - не до конца изжитое наследство react: некоторые вещи с vue можно было бы сделать лаконичней.

Если хочется своего - абстрагируй во что-то подобное, покрывай тестами и переиспользуй.
Чтоб не было гонок - проверяй актуальность после каждого await.
Чтоб не было проблем с индикатором загрузки - следует использовать под капотом не логический, а числовой (абстракцию надо оным) loading++ loading-- loading===0 - если такой "застрянет", значит где-то ошибка в логике.:)
Скрыть(абстрагировать) передачу AbortSignal в axios можно с помощью интерцептора.

В общем и целом к сожалению в спеках async(Promise) отмены в красивом виде не предусмотрели.
Есть пример того как хотелось бы чтоб оно работало - "flow" с использованием генераторов (yield вместо async + обёртки), которое позволяет остановить всё на любом моменте извне, ничего не пробрасывая и не подготавливая. Однако это костыль, требует всяких уродливых обёрток, не очевидного поведения и плоховато дружит с типизацией.
Я всё порываюсь состряпать какой-нить "DeepCancelablePromise" на магии, и заставить его работать похожим образом, но руки не доходят.:)
Ответ написан
Комментировать
@null_object
Можно начать использовать composables. В примере из vueuse взял, можно и самому реализовать

refDebounced
useAsyncState

import { refDebounced, useAsyncState } from '@vueuse/core';
import { onWatcherCleanup, ref, watch } from 'vue';

async function suggest(place: string, opts?: RequestInit) {
  const response = await fetch(..., opts);

  return response.json();
}

const places = useAsyncState<Place[]>(
  (dst: string, opts?: RequestInit) => suggest(dst, opts),
  [],
  {
    onError(error: unknown) {
      if (error instanceof Error && error.name !== 'AbortError')
        console.error('Failed to fetch places:', error);
    },
  }
);

const destination = ref('');
const debouncedDestination = refDebounced(destination, 100);

watch(debouncedDestination, (dst) => {
  const controller = new AbortController();

  places.executeImmediate(dst, { signal: controller.signal });

  onWatcherCleanup(() => {
    controller.abort();
  });
});
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы