Service Worker
Service Worker nima, u oddiy Web Worker'dan qanday farq qiladi, install/activate lifecycle bosqichlari, "waiting" holati va skipWaiting, fetch event orqali proxy sifatida ishlashi, Cache API va Cache First / Network First / Stale-While-Revalidate strategiyalari.
84-dars
Service Worker
Web Worker'ni eslang: u — hisoblashni background'ga olib chiqadi, lekin tab yopilsa, u ham o'ladi, va u faqat shu tabga bog'liq.
Service Worker — bundan tubdan farq qiladi. U network va tab o'rtasida turuvchi proxy — ya'ni sizning ilovangiz fetch qilganda, so'rov to'g'ridan-to'g'ri internetga emas, balki avval Service Worker orqali o'tadi. Bu unga uchta noyob imkoniyat beradi:
- Offline ishlash — internet yo'q bo'lsa ham, keshdan javob qaytarish
- Push notification — tab yopiq bo'lsa ham, brauzer fon'da xabar ko'rsatishi
- Background sync — internet qaytganda, kechiktirilgan so'rovlarni avtomatik yuborish
1. Asosiy farq — lifecycle
Oddiy Worker: tab ochiq — u yashaydi, tab yopildi — u o'ladi. Juda oddiy, chiziqli.
Service Worker: tab bilan bog'liq emas. U brauzerning o'zida, alohida, "ilova process'idan tashqarida" ro'yxatdan o'tadi va yashaydi — hatto siz sahifani yopib qo'ysangiz ham.
// main.js — sahifada, oddiy script
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('SW ro\'yxatdan o\'tdi:', reg))
.catch(err => console.error('Xato:', err));
}
Bu yerda register('/sw.js') chaqirilganda, brauzer uchta aniq bosqichdan o'tadi:
Scope — Service Worker qaysi sahifalarni nazorat qiladi
Service Worker faqat o'zi ro'yxatdan o'tgan joydan pastdagi (yoki teng darajadagi) URL'larni nazorat qila oladi — bu uning scopei deb ataladi. Standart holatda scope — sw.js fayli joylashgan papka:
navigator.serviceWorker.register('/sw.js');
// scope avtomatik '/' bo'ladi — butun sayt nazorat ostida
navigator.serviceWorker.register('/blog/sw.js');
// scope avtomatik '/blog/' bo'ladi — faqat /blog/... sahifalar nazorat ostida,
// masalan /about sahifasi bu SW'ga umuman tegishli bo'lmaydi
Scope'ni register()ning ikkinchi argumenti orqali qo'lda ham belgilash mumkin — lekin faqat fayl joylashgan papka yoki undan pastroq (tor) qilib, hech qachon kengroq qilib bo'lmaydi:
navigator.serviceWorker.register('/scripts/sw.js', { scope: '/' });
// fayl /scripts/ ichida tursa ham, scope'ni butun saytga ('/') kengaytirdik
Nega bu cheklov bor? Xavfsizlik uchun: agar /scripts/sw.js scope'ni /'gacha kengaytira olsa-yu, /scripts/ papkasiga yozish huquqi faqat ba'zi dasturchilarda bo'lsa, xatarli bo'lardi. Aksincha — sw.js qanchalik chuqurroq joylashgan bo'lsa, u shunchalik tor scope'ni tanlashi mumkin (masalan, /blog/sw.jsni { scope: '/blog/posts/' } bilan yanada torroq qilish mumkin), lekin hech qachon undan tashqariga chiqib ketolmaydi.
Amalda bu shuni anglatadi: agar loyihada bir nechta mustaqil Service Worker kerak bo'lsa (masalan, /blog/ va /shop/ uchun alohida-alohida offline mantiq), ularni mos ravishda /blog/sw.js va /shop/sw.js qilib joylashtirish kifoya — brauzer avtomatik ravishda har birini faqat o'z papkasiga bog'laydi.
Ro'yxatdan o'tgan scope'ni tekshirish uchun:
navigator.serviceWorker.register('/blog/sw.js').then(reg => {
console.log(reg.scope); // "https://sayt.uz/blog/"
});
1-bosqich: Download
Brauzer sw.js faylini yuklab oladi. Agar bu URL avval ro'yxatdan o'tgan bo'lsa, brauzer eski va yangi faylni bayt-bayt taqqoslaydi — bir harf ham farqlansa, "yangi versiya" deb hisoblanadi.
2-bosqich: Install
// sw.js
self.addEventListener('install', (event) => {
console.log('Install bosqichi');
event.waitUntil(
caches.open('v1').then(cache => {
return cache.addAll(['/index.html', '/style.css', '/app.js']);
})
);
});
install — Service Worker birinchi marta ro'yxatdan o'tganda (yoki yangi versiya paydo bo'lganda) ishga tushadi. Bu yerda odatda kerakli fayllar keshga oldindan yuklab qo'yiladi (pre-caching).
event.waitUntil(promise) — muhim mexanizm: u brauzerga aytadi — "bu Promise tugamaguncha, install bosqichini 'tugadi' deb hisoblama". Agar buni yozmasangiz, brauzer keshlash tugashini kutmasdan keyingi bosqichga o'tib ketishi mumkin.
3-bosqich: Activate
self.addEventListener('activate', (event) => {
console.log('Activate bosqichi');
event.waitUntil(
caches.keys().then(keys => {
return Promise.all(
keys.filter(key => key !== 'v1').map(key => caches.delete(key))
);
})
);
});
activate — yangi Service Worker eski versiyani almashtirib, nazoratni qo'lga olganda ishga tushadi. Bu yerda odatda eski keshlarni tozalash ishlari qilinadi.
2. Nega ikkita bosqich kerak — "waiting" holati
Bu yerda eng ko'p chalkashtiradigan narsa: yangi Service Worker installdan o'tgach, u darhol eskisini almashtirmaydi. U "waiting" holatida to'xtab qoladi:
Eski SW (v1) — hali barcha ochiq tab'larni boshqaryapti
|
Yangi SW (v2) — install bo'ldi, lekin "waiting"da kutyapti
|
Faqat BARCHA eski tab'lar yopilgandan keyin →
|
v2 activate bo'ladi va nazoratni oladi
Bunga sabab — agar sahifa v1 kodini ishlatib turgan bo'lsa-yu, fon'da v2 birdan boshqarishni boshlab yuborsa, eski sahifa yangi keshlangan (yoki formatga mos kelmaydigan) resurslarni olishi mumkin — bu versiyalar aralashib ketishiga olib keladi. Shuning uchun brauzer xavfsizlik uchun eski tab'lar yopilishini kutadi.
Buni majburiy tezlashtirish mumkin:
self.addEventListener('install', (event) => {
self.skipWaiting(); // "waiting"ni o'tkazib yuborish, darhol activate'ga o'tish
});
3. Asosiy mexanizm — fetch event (proxy sifatida ishlashi)
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then(cachedResponse => {
if (cachedResponse) {
return cachedResponse; // keshdan qaytaramiz, network umuman ishlatilmaydi
}
return fetch(event.request); // keshda yo'q — internetga chiqamiz
})
);
});
event.respondWith(promise) — bu brauzerga aytadi: "bu so'rovga javobni odatdagidek internetdan emas, men beraman". Bu — Service Worker'ning "proxy" bo'lishining texnik ma'nosi.
4. Chuqurroq — bu qanday arxitektura darajasida ishlaydi (browser engine)
Bu yerda muhim tushuncha bor: Service Worker — bu oddiy Web Worker emas, u butunlay boshqa poydevorga qurilgan:
| Web Worker | Service Worker | |
|---|---|---|
| Kim boshqaradi | Tab (JS process) | Brauzerning network stacki |
| Qachon ishga tushadi | new Worker() chaqirilganda |
Tegishli event (fetch, push, sync) kelganda |
| Doim ishlaydimi | Ha, yaratilgandan o'lguncha | Yo'q — event yo'q bo'lsa, o'ldiriladi |
Bu oxirgi qator — eng muhim texnik nuqta: Service Worker doimiy jonli thread emas. Brauzer uni event-driven, "ephemeral" (vaqtinchalik) qilib ishga tushiradi:
- fetch (yoki push, sync) eventi keladi
- Agar Service Worker "uxlab" (terminated) yotgan bo'lsa, brauzer uni qayta ishga tushiradi (spin up) — bu yangi V8 kontekst yaratish, kodni qayta bajarish
- Event handler ishlaydi
- Agar bir necha soniya davomida yangi event kelmasa, brauzer xotira tejash uchun Service Worker'ni yana o'chiradi (terminate)
Bu — nega Service Worker kodida global o'zgaruvchilarga ishonib bo'lmasligining sababi:
let counter = 0; // XATO YONDASHUV
self.addEventListener('fetch', (event) => {
counter++; // Bu doim ishonchli o'smaydi — SW o'rtada o'chirilib qayta tug'ilishi mumkin
});
Har safar qayta ishga tushganda, counter 0'ga qaytadi — chunki butun JS konteksti (V8 heap'i) tozalanadi. Doimiy holat kerak bo'lsa, IndexedDB ishlatiladi (keyingi mavzu) — u Service Worker o'chib-yonganda ham saqlanib qoladi.
5. Cache API — nega localStorage emas
localStorage sinxron (ishlaydi va thread'ni bloklaydi), va Service Worker'da umuman mavjud emas (chunki u thread'ni bloklashi mumkin, bu esa event-driven arxitekturaga zid). Shuning uchun maxsus Cache API ishlatiladi — bu to'liq asinxron, Promise-based:
caches.open('v1').then(cache => {
cache.put('/api/data', new Response(JSON.stringify({ x: 1 })));
});
Cache obyekti — bu aslida Request → Response juftliklarining xaritasi (map), brauzer diskida saqlanadi, Response obyektining o'zi (headers, status, body) to'liq saqlanadi — shunchaki matn emas.
6. Cache API metodlari — to'liq ro'yxat
Cache API ikki qavatli: caches — barcha keshlar ustidan ishlaydigan global obyekt (CacheStorage), cache — caches.open() orqali olingan bitta aniq kesh.
CacheStorage metodlari (caches.*)
| Metod | Vazifasi | Qanday ishlatiladi |
|---|---|---|
caches.open(name) |
Berilgan nomdagi keshni ochadi; mavjud bo'lmasa — yaratadi. Cache obyektiga Promise qaytaradi. | caches.open('v1').then(cache => { /* cache bilan ishlash */ }); |
caches.match(request) |
Barcha keshlar bo'ylab qidiradi (nom ko'rsatilmaydi) — birinchi mos Responseni topib qaytaradi. Qaysi keshda ekanini bilish shart bo'lmaganda qulay. | caches.match('/index.html').then(response => { if (response) return response; }); |
caches.has(name) |
Berilgan nomdagi kesh mavjudligini tekshiradi, true/false qaytaradi. |
caches.has('v1').then(exists => console.log(exists)); // true |
caches.delete(name) |
Butun keshni (ichidagi hammasi bilan) o'chiradi. Muvaffaqiyatli bo'lsa true qaytaradi. Odatda activate bosqichida eski versiyalarni tozalashda ishlatiladi. |
caches.delete('v0').then(deleted => console.log(deleted)); // true |
caches.keys() |
Mavjud barcha keshlarning nomlari ro'yxatini (string[]) qaytaradi. |
caches.keys().then(names => console.log(names)); // ['v1', 'v2'] |
Cache metodlari (cache.*, ya'ni caches.open() natijasi)
| Metod | Vazifasi | Qanday ishlatiladi |
|---|---|---|
cache.match(request, options?) |
Shu bitta kesh ichida so'rovga mos Responseni qidiradi. Topilmasa undefined qaytaradi. |
cache.match('/style.css').then(response => { if (response) return response; }); |
cache.matchAll(request?, options?) |
Bir nechta mos keluvchi Responselarni massiv qilib qaytaradi (masalan, query-parametr farq qiladigan bir nechta variant bo'lsa). Argument berilmasa — keshdagi hammasi qaytadi. | cache.matchAll('/api/data').then(responses => console.log(responses.length)); |
cache.add(request) |
Berilgan URL'ni fetch qiladi va natijani avtomatik keshga yozadi — fetch + putning qisqartmasi. Faqat muvaffaqiyatli (status 200) javoblar saqlanadi. |
cache.add('/logo.png'); // fetch qilib, o'zi keshga yozadi |
cache.addAll(requestsArray) |
add()ning ko'plik varianti — bir nechta URL'ni bitta chaqiruvda yuklab, keshlaydi. Muhim: agar ulardan bittasi ham muvaffaqiyatsiz bo'lsa (masalan, 404), hech nima keshlanmaydi (all-or-nothing, atomik operatsiya). |
cache.addAll(['/index.html', '/style.css', '/app.js']); |
cache.put(request, response) |
Tayyor Request/Response juftligini qo'lda keshga yozadi. add()dan farqi — bu yerda Responseni siz o'zingiz (masalan, fetch() orqali) olib kelasiz, put faqat saqlaydi, fetch qilmaydi. |
fetch('/api/data').then(response => cache.put('/api/data', response.clone())); |
cache.delete(request, options?) |
Berilgan so'rovga mos yozuvni keshdan o'chiradi. Topilib o'chirilsa true, topilmasa false qaytaradi. |
cache.delete('/old-file.js').then(deleted => console.log(deleted)); |
cache.keys(request?, options?) |
Kesh ichidagi barcha Request obyektlarini (kalitlarni) qaytaradi — qaysi URL'lar saqlanganini ko'rish uchun. | cache.keys().then(requests => requests.forEach(req => console.log(req.url))); |
match/matchAll/delete/keysning options parametri
| Option | Vazifasi | Qanday ishlatiladi |
|---|---|---|
ignoreSearch |
true bo'lsa, URL'dagi ?query=parametr qismini e'tiborsiz qoldiradi (/page?id=1 va /page?id=2 bir xil deb hisoblanadi). |
cache.match('/page?id=1', { ignoreSearch: true }); // /page?id=999 ham mos keladi |
ignoreMethod |
true bo'lsa, HTTP metodni (GET/POST) tekshirmaydi. |
cache.match(request, { ignoreMethod: true }); // POST so'rov ham GET bilan keshlangan javobga mos keladi |
ignoreVary |
true bo'lsa, Vary headerini hisobga olmaydi (odatda Vary bir xil URL, lekin turli javoblarni farqlash uchun ishlatiladi). |
cache.match(request, { ignoreVary: true }); // Vary header'dagi farqni e'tiborsiz qoldiradi |
7. Strategiyalar — qachon kesh, qachon network
Real loyihalarda bir nechta klassik strategiya bor:
| Strategiya | Mantiq | Qachon ishlatiladi |
|---|---|---|
| Cache First | Avval keshga qara, bo'lmasa network | Statik fayllar (CSS, logo) |
| Network First | Avval networkka urin, xato bo'lsa kesh | Yangilanib turadigan API data |
| Stale-While-Revalidate | Keshdan darhol qaytar, fon'da yangilab qo'y | Yangilik lentasi kabi |
Stale-While-Revalidate misoli — eng "aqlli" yondashuv:
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.open('v1').then(cache => {
return cache.match(event.request).then(cachedResponse => {
const fetchPromise = fetch(event.request).then(networkResponse => {
cache.put(event.request, networkResponse.clone());
return networkResponse;
});
// foydalanuvchiga DARHOL eski data ko'rsatiladi,
// fon'da esa yangisi yuklanib, keshga yoziladi
return cachedResponse || fetchPromise;
});
})
);
});
Diqqat: networkResponse.clone() — chunki Response body'si faqat bir marta o'qilishi mumkin (stream). Bir nusxasi keshga yoziladi, ikkinchisi sahifaga qaytariladi.
Xulosa
Web Worker — CPUni bo'shatadi (hisoblash uchun), Service Worker — networkni boshqaradi (offline va kesh uchun), va ikkalasi ham asosiy printsipda bir xil — main thread'dan mustaqil, alohida V8 konteksti, lekin Service Worker'ning hayot davri event'larga bog'liq, doimiy emas.