Pull to refresh

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

Reading time3 min
Views3.2K

В один из прекрасных рабочих дней мне прилетела задачка на то, что необходимо исправить очередной баг. Проблема заключалась в следующем: на сайте при заказе товара можно было выбрать временной интервал для доставки, и если клиент задумался или отошел на n-ое количество времени, то он не мог сделать заказ, так как временной интервал на сервере обновился, и стал недоступен, а на клиенте остались старые интервалы.

На первый взгляд, данную задачу решить максимально просто, сделать запрос, получить новые интервалы, обновить состояние (работаем на react) и отрисовать. Вот и мне показалось, что решение займет пару часов.

Итак, я приступил, начал с того, что позвонил backend разработчику, что бы узнать по какому принципу обновляются интервалы на сервере. В итоге выяснилось, что они обновляются каждые 15 минут, и по его словам, в скрипте много бизнес-логики и отрабатывает он довольно долго.

Было принято решение, чтобы не нагружать сервер делать запрос раз в 15 минут, получать актуальные данные и все прекрасно. Спустя несколько минут код был написан и осталось лишь дождаться обновления интервалов на сервере и запроса.

После проверки стало понятно, что необходимо учесть промежуток времени, до истечения 15 минутного интервала. Например, первый интервал, который будет доступен клиенту это с 14:00 до 14:15, интервалы должны обновиться в 13:45 и первым доступным интервалом должно стать время с 14:15 до 14:30. Таким образом, если клиент будет делать заказ в 13:40, то следующий запрос будет в 13:55, соответственно у нас 10 минут будут висеть неактуальные интервалы.

Что с этим делать? Нужно высчитать время до первого интервала от текущего времени, вычесть 15 минут и сделать первый запрос по истечению этого времени, а дальше, как и до этого, каждые 15 минут. В целом неплохая идея, как мне тогда показалось, я приступил к реализации:

  const timingUpdate = () => {
    if (checkout.intervals) {
      let first = null;
      for (let key in checkout.intervals) {
        if (!first) {
          first = checkout.intervals[key][0][0].split(":");
        }
      }
      let currentTime = new Date();
      currentTime = (currentTime.getHours() * 3600 + currentTime.getMinutes() * 60 + currentTime.getSeconds());

      return (+first[0] * 3600 + +first[1] * 60 - currentTime - 14.9 * 60) * 1000;
    }
  };
  
	useEffect(() => {
    const currentTiming = timingUpdate();

    let onInterval = setTimeout(() => {
      if (checkout.currentRestaurant) {
        dispatch(fetchRestaurantIntervals({ restaurant_id: currentRestaurant.id }));
      }
    }, currentTiming);

    return () => {
      clearTimeout(onInterval);
    };
  }, [intervals]);

Хотелось бы отметить, что пришлось повозиться, так как с сервера приходил объект а не массив и первый интервал вычленять было довольно неприятно. Так же, можно заметить, что запрос я делаю не через 15 минут ровно, а через 15,1 чтобы дать время отработать "тяжелому" скрипту на сервере. Мои скромные мануальные тесты были пройдены и я отправил задачу на тестирование.

Спустя неделю, тестировщик дал сценарий, при котором интервалы не обновлялись и заказ было невозможно сделать, потом последовало несколько созвонов с тестировщиком и backend разработчиком, и как выяснилось, интервалы обновляются на сервере не всегда через 15 минут, а в зависимости от загрузки курьеров. Время обновления присылать с сервера оказалось невозможным.

Я начал терзаться муками, делать вебсокет для такой простой задачи не хотелось, но и слать запросы каждую минуту казалось не логичным. Вообщем, лучшее враг хорошего, поэтому решил, что от запроса раз в минуту сервер не упадет.

  const timingUpdate = () => {
    let timeUpdate = new Date();
    return (65 - timeUpdate.getSeconds()) * 1000;
  };
  
  useEffect(() => {
    let currentTiming = timingUpdate();
    let onInterval = null;

    let timer = () => {
      currentTiming = timingUpdate();
      
      if (checkout.currentRestaurant) {
        dispatch(fetchRestaurantIntervals({ restaurant_id: currentRestaurant.id }, intervals));
      }
    };

    let onTimeout = setTimeout(() => {
      timer();
      clearInterval(onInterval);
      onInterval = setInterval(timer, currentTiming);
    }, currentTiming);

    return () => {
      clearInterval(onInterval);
      clearTimeout(onTimeout);
    };
  }, [intervals]);

Пришлось немного повозиться с setTimeout и setInterval, так как теперь, если у нас не приходил ответ, то состояние не менялось, перерендера не было и соответственно setTimeout не запускался. Поэтому я использовал setTimeout для запуска первого запроса, который срабатывал через несколько секунд, когда наступала новая минута, и далее через setInterval каждую минуту. Так же можно заметить, что я сделал дополнительные 5 секунд на отработку "тяжелого" скрипта на и обновления интервалов на сервере(Как показали эксперименты, 5 секунд было всегда достаточно). Ну и конечно же нужно не забыть очистить setTimeout и setInterval, иначе при изменении состояний различных кнопочек на странице мы получим огромное количество перерендеров.

Резюмируем: Не всегда простая на вид задача, на самом деле простая! Ну, и конечно же, лучше 10 раз переспросить как работает backend, прежде чем пытаться быстрее реализовать frontend, чтобы не делать двойную работу.

P.S.: Первая статья, не судите строго!

Tags:
Hubs:
Total votes 3: ↑0 and ↓3-3
Comments6

Articles