В один из прекрасных рабочих дней мне прилетела задачка на то, что необходимо исправить очередной баг. Проблема заключалась в следующем: на сайте при заказе товара можно было выбрать временной интервал для доставки, и если клиент задумался или отошел на 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.: Первая статья, не судите строго!
ссылка на оригинал статьи https://habr.com/ru/post/655901/
Добавить комментарий