Ein tiefer Einblick in die Frontend Web Lock API, ihre Synchronisationsprimitive und praktische Beispiele zur Verwaltung von gleichzeitigem Zugriff in Webanwendungen.
Frontend Web Lock API: Primitive zur Ressourcensynchronisation
Das moderne Web wird immer komplexer, und Anwendungen laufen oft über mehrere Tabs oder Fenster hinweg. Dies führt zur Herausforderung, den konkurrierenden Zugriff auf geteilte Ressourcen zu verwalten, wie z.B. Daten, die im localStorage, IndexedDB oder sogar auf serverseitigen Ressourcen, auf die über APIs zugegriffen wird, gespeichert sind. Die Web Lock API bietet einen standardisierten Mechanismus zur Koordinierung des Zugriffs auf diese Ressourcen, um Datenkorruption zu verhindern und die Datenkonsistenz zu gewährleisten.
Die Notwendigkeit der Ressourcensynchronisation verstehen
Stellen Sie sich ein Szenario vor, in dem ein Benutzer Ihre Webanwendung in zwei verschiedenen Tabs geöffnet hat. Beide Tabs versuchen, denselben Eintrag im localStorage zu aktualisieren. Ohne eine ordnungsgemäße Synchronisation könnten die Änderungen des einen Tabs die des anderen überschreiben, was zu Datenverlust oder Inkonsistenzen führen würde. Hier kommt die Web Lock API ins Spiel.
Die traditionelle Webentwicklung stützt sich auf Techniken wie optimistisches Sperren (Überprüfung auf Änderungen vor dem Speichern) oder serverseitiges Sperren. Diese Ansätze können jedoch komplex in der Implementierung sein und sind möglicherweise nicht für alle Situationen geeignet. Die Web Lock API bietet eine einfachere, direktere Möglichkeit, den konkurrierenden Zugriff vom Frontend aus zu verwalten.
Einführung in die Web Lock API
Die Web Lock API ist eine Browser-API, die es Webanwendungen ermöglicht, Sperren für Ressourcen zu erwerben und freizugeben. Diese Sperren werden innerhalb des Browsers gehalten und können auf einen bestimmten Ursprung (Origin) beschränkt werden, um sicherzustellen, dass sie nicht mit anderen Websites in Konflikt geraten. Die API bietet zwei Haupttypen von Sperren: exklusive Sperren und geteilte Sperren.
Exklusive Sperren
Eine exklusive Sperre gewährt exklusiven Zugriff auf eine Ressource. Nur ein Tab oder Fenster kann zu einem Zeitpunkt eine exklusive Sperre für einen bestimmten Namen halten. Dies eignet sich für Operationen, die die Ressource modifizieren, wie das Schreiben von Daten in den localStorage oder das Aktualisieren einer serverseitigen Datenbank.
Geteilte Sperren
Eine geteilte Sperre ermöglicht es mehreren Tabs oder Fenstern, gleichzeitig eine Sperre für eine Ressource zu halten. Dies eignet sich für Operationen, die die Ressource nur lesen, wie z.B. die Anzeige von Daten für den Benutzer. Geteilte Sperren können von mehreren Clients gleichzeitig gehalten werden, aber eine exklusive Sperre blockiert alle geteilten Sperren und umgekehrt.
Verwendung der Web Lock API: Eine praktische Anleitung
Der Zugriff auf die Web Lock API erfolgt über die Eigenschaft navigator.locks. Diese Eigenschaft bietet Zugriff auf die Methoden request() und query().
Anfordern einer Sperre
Die request()-Methode wird verwendet, um eine Sperre anzufordern. Sie benötigt den Namen der Sperre, ein optionales Options-Objekt und eine Callback-Funktion. Die Callback-Funktion wird erst ausgeführt, nachdem die Sperre erfolgreich erworben wurde. Das Options-Objekt kann den Sperrmodus ('exclusive' oder 'shared') und ein optionales ifAvailable-Flag angeben.
Hier ist ein grundlegendes Beispiel für das Anfordern einer exklusiven Sperre:
navigator.locks.request('my-resource', { mode: 'exclusive' }, async lock => {
try {
// Führen Sie Operationen aus, die exklusiven Zugriff auf die Ressource erfordern
console.log('Sperre erworben!');
// Simulieren einer asynchronen Operation
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('Sperre wird freigegeben.');
} finally {
// Die Sperre wird automatisch freigegeben, wenn die Callback-Funktion zurückkehrt oder einen Fehler auslöst
// Sie können sie aber auch manuell freigeben (obwohl dies im Allgemeinen nicht notwendig ist).
// lock.release();
}
});
In diesem Beispiel versucht die request()-Methode, eine exklusive Sperre mit dem Namen 'my-resource' zu erwerben. Wenn die Sperre verfügbar ist, wird die Callback-Funktion ausgeführt. Innerhalb des Callbacks können Sie Operationen durchführen, die exklusiven Zugriff auf die Ressource erfordern. Die Sperre wird automatisch freigegeben, wenn die Callback-Funktion zurückkehrt oder einen Fehler auslöst. Der finally-Block stellt sicher, dass jeglicher Aufräumcode ausgeführt wird, auch wenn ein Fehler auftritt.
Hier ist ein Beispiel unter Verwendung der `ifAvailable`-Option:
navigator.locks.request('my-resource', { mode: 'exclusive', ifAvailable: true }, lock => {
if (lock) {
console.log('Sperre sofort erworben!');
// Operationen mit der Sperre durchführen
} else {
console.log('Sperre nicht sofort verfügbar, mache etwas anderes.');
// Alternative Operationen durchführen
}
}).catch(error => {
console.error('Fehler beim Anfordern der Sperre:', error);
});
Wenn `ifAvailable` auf `true` gesetzt ist, wird das `request`-Promise sofort mit dem Sperrobjekt aufgelöst, falls die Sperre verfügbar ist. Wenn die Sperre nicht verfügbar ist, wird das Promise mit `undefined` aufgelöst. Die Callback-Funktion wird unabhängig davon ausgeführt, ob eine Sperre erworben wurde, sodass Sie beide Fälle behandeln können. Es ist wichtig zu beachten, dass das an die Callback-Funktion übergebene Sperrobjekt `null` oder `undefined` ist, wenn die Sperre nicht verfügbar ist.
Das Anfordern einer geteilten Sperre ist ähnlich:
navigator.locks.request('my-resource', { mode: 'shared' }, async lock => {
try {
// Führen Sie schreibgeschützte Operationen auf der Ressource durch
console.log('Geteilte Sperre erworben!');
// Simulieren einer asynchronen Leseoperation
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Geteilte Sperre wird freigegeben.');
} finally {
// Sperre wird automatisch freigegeben
}
});
Überprüfen des Sperrstatus
Die query()-Methode ermöglicht es Ihnen, den aktuellen Status von Sperren zu überprüfen. Sie gibt ein Promise zurück, das mit einem Objekt aufgelöst wird, das Informationen über die aktiven Sperren für den aktuellen Ursprung enthält.
navigator.locks.query().then(lockInfo => {
console.log('Sperrinformationen:', lockInfo);
if (lockInfo.held) {
console.log('Sperren werden aktuell gehalten:');
lockInfo.held.forEach(lock => {
console.log(` Name: ${lock.name}, Modus: ${lock.mode}`);
});
} else {
console.log('Aktuell werden keine Sperren gehalten.');
}
if (lockInfo.pending) {
console.log('Anstehende Sperranfragen:');
lockInfo.pending.forEach(request => {
console.log(` Name: ${request.name}, Modus: ${request.mode}`);
});
} else {
console.log('Keine anstehenden Sperranfragen.');
}
});
Das lockInfo-Objekt enthält zwei Eigenschaften: held und pending. Die held-Eigenschaft ist ein Array von Objekten, von denen jedes eine aktuell vom Ursprung gehaltene Sperre darstellt. Jedes Objekt enthält den name und mode der Sperre. Die `pending`-Eigenschaft ist ein Array von Sperranforderungen, die in der Warteschlange stehen und darauf warten, gewährt zu werden.
Fehlerbehandlung
Die request()-Methode gibt ein Promise zurück, das bei einem Fehler abgelehnt werden kann. Häufige Fehler sind:
AbortError: Die Sperranforderung wurde abgebrochen.SecurityError: Die Sperranforderung wurde aufgrund von Sicherheitseinschränkungen verweigert.
Es ist wichtig, diese Fehler zu behandeln, um unerwartetes Verhalten zu verhindern. Sie können einen try...catch-Block verwenden, um Fehler abzufangen:
navigator.locks.request('my-resource', { mode: 'exclusive' }, lock => {
// ...
}).catch(error => {
console.error('Fehler beim Anfordern der Sperre:', error);
// Behandeln Sie den Fehler entsprechend
});
Anwendungsfälle und Beispiele
Die Web Lock API kann in einer Vielzahl von Szenarien verwendet werden, um den konkurrierenden Zugriff auf geteilte Ressourcen zu verwalten. Hier sind einige Beispiele:
Verhindern von gleichzeitigen Formularübermittlungen
Stellen Sie sich ein Szenario vor, in dem ein Benutzer versehentlich mehrmals auf den Senden-Button eines Formulars klickt. Dies könnte dazu führen, dass mehrere identische Übermittlungen verarbeitet werden. Die Web Lock API kann verwendet werden, um dies zu verhindern, indem vor dem Absenden des Formulars eine Sperre erworben und nach Abschluss der Übermittlung wieder freigegeben wird.
async function submitForm(formData) {
try {
await navigator.locks.request('form-submission', { mode: 'exclusive' }, async lock => {
console.log('Formular wird übermittelt...');
// Formularübermittlung simulieren
await new Promise(resolve => setTimeout(resolve, 3000));
console.log('Formular erfolgreich übermittelt!');
});
} catch (error) {
console.error('Fehler beim Übermitteln des Formulars:', error);
}
}
// Hängen Sie die submitForm-Funktion an das submit-Ereignis des Formulars an
const form = document.getElementById('myForm');
form.addEventListener('submit', async (event) => {
event.preventDefault(); // Standard-Formularübermittlung verhindern
const formData = new FormData(form);
await submitForm(formData);
});
Verwalten von Daten im localStorage
Wie bereits erwähnt, kann die Web Lock API verwendet werden, um Datenkorruption zu verhindern, wenn mehrere Tabs oder Fenster auf dieselben Daten im localStorage zugreifen. Hier ist ein Beispiel, wie man einen Wert im localStorage mit einer exklusiven Sperre aktualisiert:
async function updateLocalStorage(key, newValue) {
try {
await navigator.locks.request(key, { mode: 'exclusive' }, async lock => {
console.log(`Aktualisiere localStorage-Schlüssel '${key}' zu '${newValue}'...`);
localStorage.setItem(key, newValue);
console.log(`localStorage-Schlüssel '${key}' erfolgreich aktualisiert!`);
});
} catch (error) {
console.error(`Fehler beim Aktualisieren des localStorage-Schlüssels '${key}':`, error);
}
}
// Beispielverwendung:
updateLocalStorage('my-data', 'new value');
Koordinierung des Zugriffs auf serverseitige Ressourcen
Die Web Lock API kann auch verwendet werden, um den Zugriff auf serverseitige Ressourcen zu koordinieren. Zum Beispiel könnten Sie eine Sperre erwerben, bevor Sie eine API-Anfrage stellen, die Daten auf dem Server modifiziert. Dies kann Race Conditions verhindern und die Datenkonsistenz gewährleisten. Sie könnten dies implementieren, um Schreiboperationen auf einen geteilten Datenbankdatensatz zu serialisieren.
async function updateServerData(data) {
try {
await navigator.locks.request('server-update', { mode: 'exclusive' }, async lock => {
console.log('Serverdaten werden aktualisiert...');
const response = await fetch('/api/update-data', {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error('Fehler beim Aktualisieren der Serverdaten');
}
console.log('Serverdaten erfolgreich aktualisiert!');
});
} catch (error) {
console.error('Fehler beim Aktualisieren der Serverdaten:', error);
}
}
// Beispielverwendung:
updateServerData({ value: 'updated value' });
Browserkompatibilität
Stand Ende 2023 hat die Web Lock API eine gute Browserunterstützung in modernen Browsern, einschließlich Chrome, Firefox, Safari und Edge. Es ist jedoch immer eine gute Idee, die neuesten Informationen zur Browserkompatibilität auf Ressourcen wie Can I use... zu überprüfen, bevor Sie die API in der Produktion einsetzen.
Sie können Funktionserkennung (Feature Detection) verwenden, um zu prüfen, ob die Web Lock API vom Browser des Benutzers unterstützt wird:
if ('locks' in navigator) {
// Web Lock API wird unterstützt
console.log('Web Lock API wird unterstützt!');
} else {
// Web Lock API wird nicht unterstützt
console.warn('Web Lock API wird in diesem Browser nicht unterstützt.');
}
Vorteile der Verwendung der Web Lock API
- Verbesserte Datenkonsistenz: Verhindert Datenkorruption und stellt sicher, dass Daten über mehrere Tabs oder Fenster hinweg konsistent sind.
- Vereinfachtes Gleichzeitigkeitsmanagement: Bietet einen einfachen und standardisierten Mechanismus zur Verwaltung des konkurrierenden Zugriffs auf geteilte Ressourcen.
- Reduzierte Komplexität: Eliminiert die Notwendigkeit für komplexe, benutzerdefinierte Synchronisationsmechanismen.
- Verbesserte Benutzererfahrung: Verhindert unerwartetes Verhalten und verbessert das allgemeine Benutzererlebnis.
Einschränkungen und Überlegungen
- Ursprungs-Geltungsbereich (Origin Scope): Sperren sind auf den Ursprung beschränkt, was bedeutet, dass sie nur für Tabs oder Fenster von derselben Domain, demselben Protokoll und demselben Port gelten.
- Potenzial für Deadlocks: Obwohl weniger anfällig als andere Synchronisationsprimitive, ist es bei unachtsamer Handhabung immer noch möglich, Deadlock-Situationen zu schaffen. Strukturieren Sie die Logik zum Erwerb und zur Freigabe von Sperren sorgfältig.
- Auf den Browser beschränkt: Sperren werden innerhalb des Browsers gehalten und bieten keine Synchronisation über verschiedene Browser oder Geräte hinweg. Für serverseitige Ressourcen muss auch der Server Sperrmechanismen implementieren.
- Asynchrone Natur: Die API ist asynchron, was eine sorgfältige Handhabung von Promises und Callbacks erfordert.
Bewährte Vorgehensweisen
- Sperren kurz halten: Minimieren Sie die Zeit, in der eine Sperre gehalten wird, um die Wahrscheinlichkeit von Konflikten zu verringern.
- Spezifische Sperrnamen verwenden: Verwenden Sie beschreibende und spezifische Sperrnamen, um Konflikte mit anderen Teilen Ihrer Anwendung oder Drittanbieter-Bibliotheken zu vermeiden.
- Fehler behandeln: Behandeln Sie Fehler angemessen, um unerwartetes Verhalten zu verhindern.
- Alternativen in Betracht ziehen: Bewerten Sie, ob die Web Lock API die beste Lösung für Ihren spezifischen Anwendungsfall ist. In einigen Fällen können andere Techniken wie optimistisches Sperren oder serverseitiges Sperren angemessener sein.
- Gründlich testen: Testen Sie Ihren Code gründlich, um sicherzustellen, dass er den konkurrierenden Zugriff korrekt handhabt. Verwenden Sie mehrere Browser-Tabs und -Fenster, um die gleichzeitige Nutzung zu simulieren.
Fazit
Die Frontend Web Lock API bietet eine leistungsstarke und bequeme Möglichkeit, den konkurrierenden Zugriff auf geteilte Ressourcen in Webanwendungen zu verwalten. Durch die Verwendung von exklusiven und geteilten Sperren können Sie Datenkorruption verhindern, die Datenkonsistenz gewährleisten und das allgemeine Benutzererlebnis verbessern. Obwohl sie Einschränkungen hat, ist die Web Lock API ein wertvolles Werkzeug für jeden Webentwickler, der an komplexen Anwendungen arbeitet, die den konkurrierenden Zugriff auf geteilte Ressourcen handhaben müssen. Denken Sie daran, die Browserkompatibilität zu berücksichtigen, Fehler angemessen zu behandeln und Ihren Code gründlich zu testen, um sicherzustellen, dass er wie erwartet funktioniert.
Durch das Verständnis der in diesem Leitfaden beschriebenen Konzepte und Techniken können Sie die Web Lock API effektiv nutzen, um robuste und zuverlässige Webanwendungen zu erstellen, die den Anforderungen des modernen Webs gewachsen sind.