Loyihalar hajmi kattalashgani sayin, shunchaki funksiyalarni amalga oshirishdan ko'ra holatni boshqarish va asinxron so'rovlarni samarali qayta ishlash muhimroq bo'ladi. Xususan, React asosidagi ilovalarda bir necha komponentlar bir xil ma'lumotlarni baham ko'rishi va turli holat o'zgarishlariga ko'ra API ni chaqirishi kerak, bunday tuzilma murakkablashgan sari kutilmagan takroriy so'rovlar muammosi yuzaga kelishi mumkin.
Ushbu loyiha doirasida login qilgandan so'ng foydalanuvchi ma'lumotlari, imtiyozlar ma'lumotlari, kirish mumkin bo'lgan ish ma'lumotlarini global Context ga saqlash va bir nechta sahnada umumiy ishlatish tuzilmasini joriy etmoqdamiz. Dastlab, hammasi to'g'ri ishlayotganga o'xshardi, lekin funksiyalar davomiy qo'shilgan sari bir xil API bir necha marta chaqirilib yoki oldingi so'rovning javobi oxirgi ma'lumotlarni ustiga yopishtirib qo'yish holatlari yuzaga keldi.
Dastlab buni server javobi tezligi muammosi deb o'yladim. Lekin asl sababni tahlil qilganda frontendning ichki holat boshqarish tuzilmasi va asinxron qayta ishlash usuli muammoli ekanligini aniqladim.
Ushbu maqolada takroriy API so'rovlarining yuzaga kelishiga olib kelgan asos va sabablarni tahlil qilish jarayoni, shuningdek, AbortController va so'rov identifikatori (runId) dan foydalanib muammoni hal qilish tajribasini baham ko'rmoqchiman.
1. Muammo holati
Loyiha doirasida login qilgandan so'ng foydalanuvchi bilan bog'liq ma'lumotlarni qidirish jarayoni mavjud edi.
Dastlabki yuklashda quyidagi ma'lumotlarni birga qidirayotgan edik.
1. Foydalanuvchining asosiy ma'lumoti
2. Foydalanuvchining imtiyoz ma'lumoti
3. Foydalanuvchi kirish mumkin bo'lgan ish ma'lumotlari
4. Loyihaga oid ma'lumotlar
Qidirilgan ma'lumotlar global Context ga saqlanadi va bir nechta sahnada umumiy ishlatiladi.
Umumiy jarayon quyidagicha edi.
Login Tugadi -> foydalanuvchi ma'lumot so'rov -> huquq ma'lumot so'rov -> loyiha ma'lumot so'rov -> Context saqlash -> ekran rendering
dastlab muammo yo'qdek bo'ldi.
Biroq loyiha kattalashgani sayin, Context ga saqlanayotgan ma'lumotlar ko'paydi va buni ishlatadigan komponentlar ham ko'paydi.
Xususan, autentifikatsiya holati, huquq holati, Context holati o'zgarish jarayonida bir xil so'rov logikasi takroran bajarilish hodisasi paydo bo'ldi.
Masalan, foydalanuvchi tizimga kirgandan keyin quyidagi jarayon sodir bo'lishi mumkin.
Tasdiqlash Holat O'zgartirish -> Foydalanuvchi Ma'lumot Qidirish -> Context yangilash -> Context obuna Komponent Qayta renderlash -> qo'shish Qidirish Ishga tushirish
Aslida Network tabini tekshirganda bir xil API qisqa vaqt ichida bir necha marta chaqirilayotganini ko'rdim.
Tayyorlash muhitida katta sezilmasa-da, ishga tushirish muhitida server yukini oshirish va tarmoq resurslarini sarflashga olib kelishi mumkin bo'lgan tuzilma edi.
2. Sabablarni tahlil qilish
Muammoni hal qilish uchun birinchi navbatda soʻrov oqimini kuzatdim.
React DevTools va Network tabini foydalanib tahlil qilish natijasida muammo frontend ichida edi.
Muammo holatlar orasidagi murakkab bogʻlanishdan kelib chiqqan edi.
Tasdiqlash Holat Oʻzgartirish -> Foydalanuvchi Ma'lumot Qidirish -> Context saqlash -> Context qiymati Oʻzgartirish -> Consumer qayta renderlash -> Boshqa useEffectni ishga tushirish -> qo'shish API chaqiruvi
Bitta holat o'zgarishi boshqa holat o'zgarishini keltirib chiqaradi va natijada bir nechta useEffect ketma-ket ishga tushishi mumkin edi.
Muammo shundaki, ushbu jarayon loyiha hajmi oshishi bilan yanada murakkablashadi.
Darhaqiqat, bir xil API juda kichik farq bilan bir necha marta chaqirilishi hodisasini tasdiqlashimiz mumkin edi.
Bu orqali oddiygina funksiyaning ishlashi va asl ish yuritish muhitida samarali ishlashi butunlay boshqa muammo ekanligini yana bir bor his qilishimiz mumkin edi.
3. Dastlabki ko'rib chiqish: Debounce qo'llanilishi mumkinligi
Takroriy chaqirish muammosini aniqlagandan so'ng, eng avvalo ko'rib chiqqan usul Debounce edi.
Debounce — ma'lum vaqt davomida qo'shimcha hodisalar yuz bermasa, faqat funksiyani ishga tushirish texnikasi.
Umuman olganda, qidiruv joyini optimallashtirishda ko'p ishlatiladi.
const debouncedValue = useDebounce(value, 300);
Dastlab, holat o'zgarishi yuz bersa ham, ma'lum vaqt davomida kutib APIni chaqirish rejasini ko'rib chiqdik.
Darhaqiqat, Debounce foydalanuvchi kiritish hodisalari takroran yuz berayotgan vaziyatlarda juda samarali usuldir.
Biroq, bu muammo kiritish hodisalari emas, Kontekstni boshlang'ich holatini o'rnatish jarayoni va tasdiqlash holati o'zgarishi jarayonida yuz beradigan muammo edi.
ya'ni so'rovlar sonini oddiygina kechiktirishdan ko'ra allaqachon bajarilayotgan so'rovlarni xavfsiz tarzda bekor qilish muhimroq deb hisobladik.
Natijada AbortController ni qo'llashga qaror qildik.
4. AbortController ni qo'llash
AbortController - bu davom etayotgan so'rovlarni bekor qilish imkonini beradigan brauzer API'sidir.
AbortController asinxron ishlarni amalga oshirish uchun signal ob'ektini taqdim etadi,
controller.abort() chaqirilganda, ushbu signalni sezuvchi barcha so'rovlar darhol bekor qilinadi.
Bir xil signalni bir nechta so'rovlarga uzatish orqali bir vaqtning o'zida bir nechta so'rovni bekor qilish mumkin.
AbortController dan foydalangan holda yangi so'rov paydo bo'lsa, mavjud so'rovni bekor qilishga mo'ljallangan implementatsiya qildik.
const controllerRef = useRef<AbortController | null>( null );
useEffect(()=>{
. . .
controllerRef.current?.abort();
const controller = new AbortController();
controllerRef.current = controller;
. . .
// 언마운트/재실행 시 abort
return () => {
controller.abort();
};
},[ctxData, auth])
Amaliy API so'rovi paytida signalni ham uzatdik.
const response = await axios.get(url, { signal:
controller.signal, });
Bu usulning afzalligi - endi zarur bo'lmagan so'rovlarni darhol to'xtatish imkoniyatidir.
Masalan, foydalanuvchi sahnani tezda o'zgartirsa yoki holat ketma-ket o'zgaradigan bo'lsa, oldingi so'rov amalda ma'nosi yo'qoladi.
AbortController ni qo'llagandan so'ng, bunday keraksiz so'rovlarni olib tashlash imkoniyatiga ega bo'ldik va tarmoq foydalanish hajmi ham kamayishini ta'minladik.
5. AbortController bilan hal qilinmagan muammolar
AbortController ni qo'llagandan so'ng, aksariyat takroriy so'rovlar muammosi hal qilindi.
Lekin test jarayonida yana bir muammoni aniqladik.
Keling, quyidagi vaziyatni misol qilib olaylik.
soʻrov A bajarish -> soʻrov B bajarish -> soʻrov B tugadi -> eng maʼlumot koʻrsatish -> soʻrov A tugatildi -> oldingi ma'lumotlar ko'rsatish
foydalanuvchi A ni bekor qildi deb o'ylaydi, ammo aslida javob qaytarish vaqti bilan bekor qilish vaqti o'rtasida to'g'ri keladigan holatlar bo'lishi mumkin.
Bu holatda eski javob yangilangan holatni ustiga yozib qo'yadi.
Bu asinxron boshqarish jarayonida tez-tez sodir bo'ladigan Race Condition muammosidir.
AbortController bilan bunday holatlarni to'liq oldini olish imkoni mavjud emas edi.
Shuning uchun qo'shimcha xavfsizlik choralari kerak deb hisobladik.
6. So'rov identifikatori (runId) boshqaruvi
Race Condition muammosini hal qilish uchun so'rov identifikatori (runId) birgalikda boshqarildi.
Yangi so'rov yuzaga kelganda raqamni oshirib, javobni qayta ishlashda hozirgi so'rov eng so'ngi so'rovmi yoki yo'qmi tekshirishga amalga oshirdik.
const runIdRef = useRef(0);
//요청이 바뀔 때 마다 번호 증가
const currentRunId = ++runIdRef.current;
Javobni qayta ishlashdan oldin quyidagicha tekshirib ko'rdik.
if ( currentRunId !== runIdRef.current ) {
//최신 요청이 아닐 시 return
return;
}
Harakatlar oqimi quyidagicha.
so'rov A runId = 1 -> so'rov B runId = 2 -> so'rov A javob 1 !== 2 e'tiborsiz -> so'rov B javob 2 === 2 aks ettirildi
Buning orqali eskirgan so'rovlarning javoblari e'tiborsiz qoldirildi va faqat eng so'nggi so'rov ekranda aks ettirildi.
Shaxsiy jihatdan, ushbu ishda eng muhim bo'lgan qism ham aynan shu jarayon edi.
Dastlab API chaqiruvlari sonini kamaytirish maqsad qilinayotgan edi, lekin aslida qaysi javobga ishonish kerakligini boshqarish muammosi yanada muhimroq edi.
7. Promise.all dan foydalangan holda parallel ishlov berish
Qo'shimcha ravishda API chaqiruvlari tuzilishini ham yaxshiladik.
Boshlanishida biz quyidagicha ketma-ket so'rovlarni qayta ishlayotgan edik.
const userInfo = await findUserInfo();
const projectInfo = await findProjectInfo();
Bu holatda birinchi so'rov tugashi bilan ikkinchi so'rov boshlanadi.
Agar har biriga 500ms sarflansa, jami 1 soniyadan ortiq vaqt kerak bo'ladi.
Lekin ikkita API o'zaro bog'liqlik yo'q edi.
Shu sababli Promise.all'dan foydalangan holda parallel ishlashga o'zgartirdik.
const [ userInfo, projectInfo ] = await Promise.all([
findUserInfo(),
findProjectInfo(),
]);
Buni amalga oshirish orqali umumiy dastlabki yuklash vaqtini qisqartirishga muvaffaq bo'ldik va foydalanuvchilar uchun tezroq ekran kirish tajribasini taqdim eta oldik.
8. Taklif natijalari
Taklif ishlaridan keyin so'rov oqimini yana bir bor tahlil qildik.
Oldin bir xil API'ning takroran chaqirilishi holatlari ko'p sodir bo'lgan bo'lsa, taklifdan keyin asosan bitta so'rov yuzaga keldi.
Bundan tashqari, avvalgi javob yangilangan ma'lumotlarni ustunlikka olib kirishi holati va ekran bir necha marta qaltirash holati ham ro'y bermadi.
Taklif effektini quyidagicha umumlashtirish mumkin.
- Bir xil API takroriy chaqirilishini kamaytirish
- Tarmoq foydalanish miqdorini kamaytirish
- Keraksiz server so'rovlarini kamaytirish
- Race Condition muammosini hal etish
- Dastlabki yuklash tezligini oshirish
- Ma'lumotlarning izchilligini oshirish
- Saqlash osonligini oshirish
Menimcha, eng katta yutuq shundaki, ayniqsa ish muhiti sharoitida yuzaga kelishi mumkin bo'lgan ma'lumotlarning muvofiqligini oldindan bartaraf etish mumkin edi.
9. Tahlil
Bu ishni amalga oshirish davomida oddiygina funksiyalarning normal ishlashi bilan samarali ishlashi umuman boshqacha masala ekanligini yana bir bor his qildim.
Boshida foydalanuvchi ma'lumotlari normal ko'rsatiladi va ekranda aks etadi, shuning uchun katta muammo yo'q deb o'yladim.
Ammo loyiha hajmi kattalashgani sari holatni boshqarish murakkablashdi va bir xil API'ning qayta-qayta chaqirilishi yoki eski javoblarning so'nggi ma'lumotlarni o'chirib qo'yishi kabi muammolar yuzaga keldi va buni tahlil qilish jarayonida React'ning rendering tuzilmasi va asinxron ishlash usuli haqida yanada chuqurroq tushuncha ega bo'ldim.
Ayniqsa ushbu ishda oddiygina API chaqiruvi sonini kamaytirishdan ko'ra so'rovning hayot tsiklini boshqarish muhimligini o'rganishim mumkin edi.
AbortController'dan foydalangan holda keraksiz so'rovlarni bekor qildim va so'rov identifikatatori (runId) orqali faqatgina so'nggi so'rovlarni aks ettirishga erishdim, shunda ishonchli ma'lumotlarni qayta ishlash tuzilmasini yaratishga muvaffaq bo'ldim.
Shuningdek, muammoni hal qilish jarayonida muntazam e'tibor bermagan redundant so'rovlar yoki Race Condition kabi masalalar asl xizmat sharoitida ishlash samaradorligini pasaytirishi va ma'lumotlar mos kelmasligiga olib kelishi mumkinligini his qildim.
Ushbu tajriba orqali React ilovalarida holatni boshqarish va asinxron ishlash oddiy bajara oladigan yoritish maydoni emas, balki xizmat sifatiga bevosita ta'sir qiluvchi muhim omil ekanligini yana bir bor tasdiqladim. Kelajakda ham funksiyalarni amalga oshirishga e'tibor qaratishdan ko'ra so'rovlar oqimi va holat o'zgarishlarini birga ko'rib, yanada ishonchli va samarali xizmatlar ishlab chiqishga doimiy ravishda o'ylab va o'rganib borishni xohlayman.
[Ma'lumot]
https://developer.mozilla.org/ko/docs/Web/API/AbortController
https://okayoon.tistory.com/entry/AbortController#google_vignette
Kancho