Web Locks API: Mengelola Akses Bersama di Browser dengan Aman dan Efisien
1. Pendahuluan
Pernahkah Anda membangun aplikasi web yang kompleks, di mana pengguna mungkin membuka aplikasi Anda di beberapa tab browser sekaligus? Atau mungkin Anda menggunakan Web Workers untuk menjalankan tugas-tugas berat di latar belakang? Jika ya, Anda mungkin pernah menghadapi tantangan yang disebut “race condition” atau masalah inkonsistensi data.
Bayangkan skenario ini:
- Dua tab mencoba mengambil data yang sama dari API secara bersamaan, menyebabkan panggilan API yang duplikat dan membuang-buang sumber daya.
- Beberapa tab mencoba memperbarui data di IndexedDB pada saat yang sama, berpotensi menyebabkan data korup atau tidak sinkron.
- Sebuah Web Worker sedang melakukan komputasi berat, dan Anda tidak ingin tab utama atau worker lain memulai komputasi serupa sebelum yang pertama selesai.
Masalah-masalah ini adalah contoh klasik dari kebutuhan akan mekanisme “locking” atau penguncian. Di dunia backend, kita sudah akrab dengan konsep distributed locking, semaphore, atau mutex untuk mengelola akses ke sumber daya bersama. Tapi bagaimana dengan di sisi klien, di dalam browser?
Di sinilah Web Locks API hadir sebagai pahlawan. API ini menyediakan mekanisme yang terstandardisasi dan aman untuk mengkoordinasikan operasi di antara tab, window, atau Web Workers yang berasal dari origin yang sama. Dengan Web Locks API, Anda bisa memastikan bahwa hanya satu “unit kerja” (tab/worker) yang memegang kendali atas sumber daya tertentu pada satu waktu, mencegah kekacauan dan menjaga integritas aplikasi Anda.
Artikel ini akan membawa Anda menyelam lebih dalam ke Web Locks API: mengapa kita membutuhkannya, bagaimana cara kerjanya, dan bagaimana Anda bisa menggunakannya untuk membangun aplikasi web yang lebih robust dan efisien. Mari kita mulai! 🚀
2. Apa Itu Web Locks API?
Web Locks API adalah antarmuka JavaScript yang memungkinkan aplikasi web untuk mendapatkan dan melepaskan “lock” pada sumber daya bernama. Secara sederhana, Anda bisa meminta akses eksklusif atau bersama ke sebuah “nama” (string identifier) yang Anda tentukan sendiri.
📌 Konsep Dasar:
- Lock: Sebuah izin untuk melakukan operasi pada sumber daya bernama.
- Nama (Name): String unik yang mengidentifikasi sumber daya yang ingin Anda kunci. Contoh:
'refresh_token_fetch','indexeddb_sync','heavy_computation'. - Mode:
'exclusive'(default): Hanya satu pihak yang bisa memegang lock dengan nama ini pada satu waktu. Jika ada yang sudah memegang lock eksklusif, permintaan lain akan menunggu atau ditolak.'shared': Beberapa pihak bisa memegang lock dengan nama ini secara bersamaan. Namun, jika ada lock eksklusif yang diminta, semua lock bersama akan menunggu hingga lock eksklusif dilepaskan. Dan sebaliknya, jika ada lock bersama yang aktif, permintaan lock eksklusif akan menunggu.
Web Locks API diimplementasikan melalui objek navigator.locks. Metode utamanya adalah navigator.locks.request().
// Sintaks dasar
navigator.locks.request(name, callback)
navigator.locks.request(name, options, callback)
Ketika Anda memanggil request(), browser akan mencoba mendapatkan lock. Jika berhasil, callback akan dieksekusi. Yang menarik adalah, lock akan secara otomatis dilepaskan ketika callback selesai dieksekusi (baik berhasil maupun gagal), atau ketika tab/window yang meminta lock ditutup. Ini sangat membantu mencegah situasi di mana lock tidak sengaja tidak dilepaskan (deadlock) karena error dalam kode.
💡 Analogi: Bayangkan Anda memiliki satu-satunya kunci untuk sebuah toilet (lock eksklusif). Hanya satu orang yang bisa menggunakan kunci itu pada satu waktu. Jika ada orang lain yang ingin menggunakan toilet, mereka harus menunggu sampai kunci dikembalikan.
Sekarang bayangkan ada beberapa tempat duduk di sebuah meja rapat (lock bersama). Beberapa orang bisa duduk di sana bersamaan. Tetapi jika ada yang ingin mempresentasikan sesuatu (membutuhkan lock eksklusif pada meja), semua yang duduk harus berdiri dulu.
3. Kenapa Kita Membutuhkan Web Locks API? (Use Cases)
Web Locks API sangat berguna untuk berbagai skenario di aplikasi web modern. Berikut adalah beberapa contoh konkret:
🎯 3.1. Mencegah Duplikasi Panggilan API
Ini adalah salah satu kasus penggunaan paling umum. Misalkan Anda memiliki logika untuk mengambil atau memperbarui token autentikasi (misalnya refresh_token). Jika pengguna membuka aplikasi Anda di dua tab, dan kedua tab mencoba me-refresh token secara bersamaan saat token lama kadaluarsa, ini bisa menyebabkan masalah:
- Panggilan API yang tidak perlu.
- Potensi invalidasi token oleh panggilan kedua jika backend hanya mengizinkan satu token aktif.
async function refreshAccessTokenWithLock() {
const lockName = 'refresh_token_fetch';
// Mencoba mendapatkan lock eksklusif
const token = await navigator.locks.request(lockName, { mode: 'exclusive' }, async (lock) => {
// Jika lock berhasil didapatkan, cek apakah token sudah diperbarui oleh tab lain
const currentToken = localStorage.getItem('access_token');
if (currentToken && !isTokenExpired(currentToken)) {
console.log('Token sudah diperbarui oleh tab lain atau masih valid.');
return currentToken;
}
console.log('Mendapatkan lock dan memulai proses refresh token...');
try {
const response = await fetch('/api/refresh-token', { method: 'POST' });
if (!response.ok) throw new Error('Gagal refresh token');
const data = await response.json();
localStorage.setItem('access_token', data.newAccessToken);
console.log('Token berhasil diperbarui.');
return data.newAccessToken;
} catch (error) {
console.error('Error saat refresh token:', error);
throw error;
}
});
// Jika lock tidak didapatkan (karena tab lain sudah memegangnya),
// navigator.locks.request akan mengembalikan undefined atau null tergantung implementasi browser
// Atau, jika lock didapatkan dan callback dieksekusi, token akan dikembalikan.
// Penting: Jika Anda menggunakan 'ifAvailable: true' dan lock tidak tersedia, callback tidak akan dijalankan.
// Di sini, tanpa 'ifAvailable', ia akan menunggu hingga lock tersedia.
if (token) {
return token;
} else {
// Ini hanya akan terjadi jika callback tidak dieksekusi sama sekali (misal jika ada opsi ifAvailable: true)
// Dalam kasus default (menunggu), callback pasti akan dieksekusi.
// Jadi, kita bisa langsung mengembalikan token dari localStorage setelah menunggu lock.
return localStorage.getItem('access_token');
}
}
// Contoh penggunaan
// refreshAccessTokenWithLock().then(token => console.log('Token final:', token));
Dalam contoh ini, navigator.locks.request akan memastikan bahwa hanya satu refreshAccessTokenWithLock yang berjalan pada satu waktu di seluruh tab/worker untuk nama lock 'refresh_token_fetch'.
🎯 3.2. Sinkronisasi Data Lintas Tab (IndexedDB, LocalStorage)
Ketika Anda memiliki data penting yang disimpan di IndexedDB atau LocalStorage, dan beberapa tab bisa memodifikasinya, Web Locks API dapat memastikan bahwa operasi penulisan tidak saling tumpang tindih.
async function updateUserSettings(newSettings) {
const lockName = 'user_settings_db_write';
await navigator.locks.request(lockName, async (lock) => {
console.log('Mendapatkan lock untuk menulis pengaturan pengguna...');
try {
// Buka IndexedDB, lakukan transaksi, simpan pengaturan
const db = await openIndexedDB(); // Fungsi helper untuk membuka DB
const transaction = db.transaction(['settings'], 'readwrite');
const store = transaction.objectStore('settings');
await store.put(newSettings, 'user_settings');
await transaction.complete;
console.log('Pengaturan pengguna berhasil diperbarui.');
} catch (error) {
console.error('Gagal memperbarui pengaturan:', error);
throw error;
}
});
}
🎯 3.3. Koordinasi Tugas Web Worker
Jika Anda memiliki Web Worker yang melakukan tugas komputasi intensif, Anda mungkin ingin memastikan bahwa hanya satu instance dari tugas tersebut yang berjalan pada satu waktu, bahkan jika ada beberapa worker atau tab yang mencoba memicu tugas yang sama.
// Di dalam Web Worker atau Main Thread
async function startHeavyComputation() {
const lockName = 'background_computation';
// Gunakan ifAvailable: true jika Anda tidak ingin menunggu dan lebih suka gagal jika lock tidak tersedia
const result = await navigator.locks.request(lockName, { mode: 'exclusive', ifAvailable: true }, async (lock) => {
if (!lock) {
console.log('Tugas komputasi berat sedang berjalan di tempat lain. Melewatkan.');
return null;
}
console.log('Memulai komputasi berat...');
// Lakukan komputasi intensif di sini
await new Promise(resolve => setTimeout(resolve, 5000)); // Simulasi komputasi 5 detik
console.log('Komputasi berat selesai.');
return 'Komputasi berhasil!';
});
return result;
}
4. Cara Kerja Web Locks API: Praktek Langsung
Mari kita lihat sintaks dan opsi yang lebih detail.
4.1. navigator.locks.request(name, options, callback)
name(string, wajib): Nama unik untuk lock.options(object, opsional):mode(string):'exclusive'(default) atau'shared'.ifAvailable(boolean): Jikatrue, lock akan didapatkan hanya jika tidak ada konflik. Jika ada konflik,callbacktidak akan dieksekusi danrequestakan me-resolve dengannull. Defaultnyafalse, yang berarti akan menunggu hingga lock tersedia.steal(boolean): Jikatruedan lock diminta dalam mode'exclusive', lock akan “dicuri” dari pemegang lock lain (yang harus melepaskan lock-nya). Ini adalah opsi yang sangat kuat dan harus digunakan dengan hati-hati karena bisa mengganggu operasi yang sedang berjalan. Defaultnyafalse.
callback(function, wajib): Fungsi yang akan dieksekusi saat lock berhasil didapatkan. Fungsi ini akan menerima objekLocksebagai argumen pertamanya. Lock akan otomatis dilepaskan saat fungsi ini selesai (resolve atau reject).- Return Value:
Promiseyang me-resolve dengan nilai yang dikembalikan olehcallback, ataunulljikaifAvailable: truedan lock tidak bisa didapatkan.
Contoh Kode: Lock Eksklusif Sederhana
async function doExclusiveTask() {
console.log('Mencoba mendapatkan lock eksklusif...');
const result = await navigator.locks.request('my_exclusive_lock', async (lock) => {
console.log('✅ Lock eksklusif didapatkan! Melakukan tugas...');
// Simulasi tugas yang memakan waktu
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('Tugas selesai. Lock akan dilepaskan secara otomatis.');
return 'Data penting telah diproses.';
});
console.log('Operasi selesai, hasil:', result);
}
// Coba jalankan ini di beberapa tab browser secara bersamaan
// Anda akan melihat bahwa tab-tab tersebut akan menunggu secara bergiliran.
doExclusiveTask();
Contoh Kode: Lock Bersama
async function doSharedReadTask() {
console.log('Mencoba mendapatkan lock bersama untuk membaca...');
const result = await navigator.locks.request('my_read_lock', { mode: 'shared' }, async (lock) => {
console.log('✅ Lock bersama didapatkan! Membaca data...');
// Simulasi tugas membaca
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Membaca selesai. Lock akan dilepaskan secara otomatis.');
return 'Data telah dibaca.';
});
console.log('Operasi membaca selesai, hasil:', result);
}
// Coba jalankan ini di beberapa tab
// Anda akan melihat semua tab bisa mendapatkan lock bersama dan membaca secara bersamaan.
doSharedReadTask();
// Sekarang, coba jalankan ini di tab ketiga saat doSharedReadTask() masih berjalan di tab lain.
// Ini akan menunggu hingga semua lock bersama dilepaskan.
async function doExclusiveWriteTask() {
console.log('Mencoba mendapatkan lock eksklusif untuk menulis...');
const result = await navigator.locks.request('my_read_lock', { mode: 'exclusive' }, async (lock) => {
console.log('⚠️ Lock eksklusif didapatkan! Menulis data (semua pembaca harus menunggu)...');
// Simulasi tugas menulis
await new Promise(resolve => setTimeout(resolve, 3000));
console.log('Menulis selesai. Lock akan dilepaskan secara otomatis.');
return 'Data telah diperbarui.';
});
console.log('Operasi menulis selesai, hasil:', result);
}
// doExclusiveWriteTask();
5. Tips dan Best Practices Menggunakan Web Locks API
Untuk memaksimalkan manfaat Web Locks API, perhatikan tips berikut:
✅ 5.1. Pilih Nama Lock yang Unik dan Deskriptif
Nama lock adalah kunci identifikasi. Pastikan nama yang Anda pilih unik untuk sumber daya yang ingin Anda lindungi dan cukup deskriptif agar mudah dipahami. Hindari nama generik yang bisa menyebabkan konflik yang tidak disengaja.
❌ navigator.locks.request('lock', ...) (Buruk)
✅ navigator.locks.request('user_session_refresh', ...) (Baik)
✅ navigator.locks.request('indexeddb_user_profile_update', ...) (Lebih baik)
✅ 5.2. Gunakan Mode yang Tepat (Exclusive vs Shared)
'exclusive': Ideal untuk operasi penulisan atau operasi lain yang tidak boleh berjalan bersamaan (misalnya, update token, migrasi database lokal).'shared': Cocok untuk operasi pembacaan di mana beberapa pembaca bisa berjalan bersamaan, tetapi pembaca harus menunggu jika ada penulisan eksklusif yang sedang atau akan terjadi.
✅ 5.3. Penanganan Error dan Timeout (dengan ifAvailable atau steal)
Jika Anda tidak ingin aplikasi Anda menunggu tanpa batas waktu untuk mendapatkan lock, gunakan opsi ifAvailable: true.
async function tryAcquireLock() {
const result = await navigator.locks.request('my_lock', { ifAvailable: true }, async (lock) => {
if (!lock) {
console.log('❌ Lock tidak tersedia saat ini.');
return 'Lock not acquired';
}
console.log('✅ Lock berhasil didapatkan.');
return 'Lock acquired';
});
console.log(result);
}
Untuk steal, gunakan dengan sangat hati-hati. Mencuri lock bisa mengganggu operasi yang sedang berjalan di tab lain dan berpotensi menyebabkan inkonsistensi data jika operasi tersebut tidak idempotensial atau tidak bisa di-rollback.
⚠️ 5.4. Hindari Deadlock
Meskipun Web Locks API dirancang untuk mengurangi risiko deadlock (dengan otomatis melepaskan lock), Anda tetap bisa membuat deadlock di level aplikasi jika Anda meminta beberapa lock secara berurutan dengan cara yang sirkular. Misalnya, Tab A meminta Lock X lalu Lock Y, sementara Tab B meminta Lock Y lalu Lock X. Jika keduanya mendapatkan lock pertama mereka secara bersamaan, mereka akan saling menunggu.
Strategi untuk menghindari deadlock:
- Selalu minta lock dalam urutan yang konsisten (jika Anda perlu beberapa lock).
- Pertimbangkan untuk meminta semua lock yang diperlukan dalam satu panggilan
requestjika memungkinkan, atau menggunakanifAvailable: truedengan strategi retry.
✅ 5.5. Gunakan Bersama async/await
Callback untuk navigator.locks.request mendukung async function, yang membuat kode Anda jauh lebih bersih dan mudah dibaca saat berinteraksi dengan Promise.
navigator.locks.request('my_lock', async (lock) => {
// Lakukan operasi asinkron di sini
await someAsyncOperation();
// ...
});
✅ 5.6. Pertimbangkan Browser Support
Web Locks API didukung dengan baik di browser modern (Chrome, Firefox, Edge, Safari). Namun, selalu periksa Can I use untuk memastikan kompatibilitas dengan target audiens Anda, terutama jika Anda melayani browser lama. Untuk browser yang tidak mendukung, Anda mungkin perlu fallback ke mekanisme lain yang lebih sederhana (dan mungkin kurang robust), atau menampilkan pesan kepada pengguna.
6. Web Locks API vs Alternatif Lain
Sebelum Web Locks API, developer sering menggunakan berbagai trik untuk sinkronisasi di browser:
localStorageevent listeners: Bisa digunakan, tetapi membutuhkan logika manual yang kompleks untuk membedakan antara “permintaan” lock dan “pelepasan” lock. Sangat rentan terhadap race condition jika tidak diimplementasikan dengan sangat hati-hati.BroadcastChannel: Bagus untuk komunikasi antar tab, tetapi tidak menyediakan mekanisme locking bawaan. Anda harus membangun logika locking di atasnya sendiri, yang bisa rumit.SharedWorker/SharedArrayBuffer+ Atomics: Ini adalah mekanisme low-level untuk konkurensi.SharedWorkermemungkinkan satu worker dibagikan antar tab, yang bisa bertindak sebagai koordinator.SharedArrayBufferdanAtomicsmemungkinkan manipulasi memori bersama secara aman. Namun, ini jauh lebih kompleks untuk diimplementasikan dan biasanya overkill untuk kebutuhan locking sederhana. Web Locks API dibangun di atas fondasi yang lebih tinggi untuk tujuan locking spesifik.
Web Locks API menawarkan solusi yang jauh lebih idiomatik, aman, dan mudah digunakan untuk kebutuhan locking di browser, menghilangkan banyak boilerplate dan potensi bug yang ada pada pendekatan alternatif.
Kesimpulan
Web Locks API adalah tambahan yang kuat untuk toolbox developer web modern. Dengan kemampuannya untuk mengkoordinasikan akses ke sumber daya bernama di antara tab, window, dan Web Workers, API ini memungkinkan kita untuk membangun aplikasi yang jauh lebih stabil, efisien, dan bebas dari masalah konkurensi seperti race condition dan duplikasi operasi.
Mulai dari mencegah panggilan API yang tidak perlu hingga memastikan integritas data di IndexedDB, Web Locks API menyediakan fondasi yang kokoh. Dengan pemahaman yang baik tentang mode exclusive dan shared, serta praktik terbaik seperti penamaan lock yang tepat dan penanganan error, Anda bisa memanfaatkan kekuatan ini untuk meningkatkan kualitas aplikasi web Anda secara signifikan.
Jadi, lain kali Anda menghadapi tantangan sinkronisasi di sisi klien, ingatlah Web Locks API. Ini adalah kunci Anda untuk membangun pengalaman pengguna yang lebih mulus dan aplikasi web yang lebih tangguh. Selamat mencoba! ✨
🔗 Baca Juga
- Sinkronisasi State Lintas Tab Browser: Membangun Aplikasi Web yang Konsisten dan Mulus
- Menguak Misteri Race Condition: Panduan Praktis Mencegah Bug Aneh di Aplikasi Web Anda
- Membangun Aplikasi Web Offline-First yang Cerdas dengan Background Sync dan Periodic Background Sync
- Memahami Database Connection Pooling: Kunci Performa dan Skalabilitas Aplikasi Web Anda