WEB-API BROWSER FRONTEND CONCURRENCY SYNCHRONIZATION RACE-CONDITION WEB-DEVELOPMENT JAVASCRIPT BEST-PRACTICES DATA-INTEGRITY USER-EXPERIENCE PERFORMANCE-OPTIMIZATION MODERN-WEB

Web Locks API: Mengelola Akses Bersama di Browser dengan Aman dan Efisien

⏱️ 14 menit baca
👨‍💻

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:

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:

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:

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)

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)

✅ 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:

✅ 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:

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