Heim Web-Frontend js-Tutorial Verbesserung des LRU-Cache mit konfigurierbarer Datenpersistenz

Verbesserung des LRU-Cache mit konfigurierbarer Datenpersistenz

Dec 26, 2024 pm 05:38 PM

Enhancing LRU Cache with Configurable Data Persistence

Aufbauend auf den Grundlagen dieses Leitfadens zum Erstellen eines In-Memory-Caches gehen wir noch einen Schritt weiter, indem wir konfigurierbare Datenpersistenz einführen. Durch die Nutzung der Adapter- und Strategiemuster entwerfen wir ein erweiterbares System, das Speichermechanismen von der Caching-Logik entkoppelt und so eine nahtlose Integration von Datenbanken oder Diensten nach Bedarf ermöglicht.

Die Vision: Entkopplung wie ein ORM

Das Ziel besteht darin, den Cache erweiterbar zu machen, ohne seine Kernlogik zu ändern. Inspiriert von ORM-Systemen beinhaltet unser Ansatz eine gemeinsame API-Abstraktion. Dadurch kann Speicher – wie localStorage, IndexedDB oder sogar eine Remote-Datenbank – mit minimalen Codeänderungen austauschbar funktionieren.

Die Basisklasse des Speicheradapters

Hier ist die abstrakte Klasse, die die API für jedes Persistenzsystem definiert:

export abstract class StorageAdapter {
  abstract connect(): Promise<void>;
  abstract add(key: string, value: unknown): Promise<void>;
  abstract get(key: string): Promise<unknown | null>;
  abstract getAll(): Promise<Record<string, unknown>>;
  abstract delete(key: string): Promise<void>;
  abstract clear(): Promise<void>;
}
Nach dem Login kopieren

Jede Speicherlösung muss diese Basisklasse erweitern, um eine konsistente Interaktion sicherzustellen. Hier ist zum Beispiel die Implementierung für IndexedDB:

Beispiel: IndexedDB-Adapter

Dieser Adapter implementiert die StorageAdapter-Schnittstelle, um Cache-Daten in einem IndexedDB-Speicher beizubehalten.

import { StorageAdapter } from './storage_adapter';

/**
 * IndexedDBAdapter is an implementation of the StorageAdapter 
 * interface designed to provide a persistent storage mechanism 
 * using IndexedDB. This adapter can be reused for other cache 
 * implementations or extended for similar use cases, ensuring 
 * flexibility and scalability.
 */
export class IndexedDBAdapter extends StorageAdapter {
  private readonly dbName: string;
  private readonly storeName: string;
  private db: IDBDatabase | null = null;

  /**
   * Initializes the adapter with the specified database and store 
   * names. Defaults are provided to make it easy to set up without 
   * additional configuration.
   */
  constructor(dbName: string = 'cacheDB', storeName: string = 'cacheStore') {
    super();
    this.dbName = dbName;
    this.storeName = storeName;
  }

  /**
   * Connects to the IndexedDB database and initializes it if 
   * necessary. This asynchronous method ensures that the database 
   * and object store are available before any other operations. 
   * It uses the `onupgradeneeded` event to handle schema creation 
   * or updates, making it a robust solution for versioning.
   */
  async connect(): Promise<void> {
    return await new Promise((resolve, reject) => {
      const request = indexedDB.open(this.dbName, 1);

      request.onupgradeneeded = (event) => {
        const db = (event.target as IDBOpenDBRequest).result;
        if (!db.objectStoreNames.contains(this.storeName)) {
          db.createObjectStore(this.storeName, { keyPath: 'key' });
        }
      };

      request.onsuccess = (event) => {
        this.db = (event.target as IDBOpenDBRequest).result;
        resolve();
      };

      request.onerror = () => reject(request.error);
    });
  }

  /**
   * Adds or updates a key-value pair in the store. This method is 
   * asynchronous to ensure compatibility with the non-blocking 
   * nature of IndexedDB and to prevent UI thread blocking. Using 
   * the `put` method ensures idempotency: the operation will 
   * insert or replace the entry.
   */
  async add(key: string, value: unknown): Promise<void> {
    await this._withTransaction('readwrite', (store) => store.put({ key, value }));
  }

  /**
   * Retrieves the value associated with a key. If the key does not 
   * exist, null is returned. This method is designed to integrate 
   * seamlessly with caching mechanisms, enabling fast lookups.
   */
  async get(key: string): Promise<unknown | null> {
    return await this._withTransaction('readonly', (store) =>
      this._promisifyRequest(store.get(key)).then((result) =>
        result ? (result as { key: string; value: unknown }).value : null
      )
    );
  }

  /**
   * Fetches all key-value pairs from the store. Returns an object 
   * mapping keys to their values, making it suitable for bulk 
   * operations or syncing with in-memory caches.
   */
  async getAll(): Promise<Record<string, unknown>> {
    return await this._withTransaction('readonly', (store) =>
      this._promisifyRequest(store.getAll()).then((results) =>
        results.reduce((acc: Record<string, unknown>, item: { key: string; value: unknown }) => {
          acc[item.key] = item.value;
          return acc;
        }, {})
      )
    );
  }

  /**
   * Deletes a key-value pair by its key. This method is crucial 
   * for managing cache size and removing expired entries. The 
   * `readwrite` mode is used to ensure proper deletion.
   */
  async delete(key: string): Promise<void> {
    await this._withTransaction('readwrite', (store) => store.delete(key));
  }

  /**
   * Clears all entries from the store. This method is ideal for 
   * scenarios where the entire cache needs to be invalidated, such 
   * as during application updates or environment resets.
   */
  async clear(): Promise<void> {
    await this._withTransaction('readwrite', (store) => store.clear());
  }

  /**
   * Handles transactions in a reusable way. Ensures the database 
   * is connected and abstracts the transaction logic. By 
   * centralizing transaction handling, this method reduces 
   * boilerplate code and ensures consistency across all operations.
   */
  private async _withTransaction<T>(
    mode: IDBTransactionMode,
    callback: (store: IDBObjectStore) => IDBRequest | Promise<T>
  ): Promise<T> {
    if (!this.db) throw new Error('IndexedDB is not connected');
    const transaction = this.db.transaction([this.storeName], mode);
    const store = transaction.objectStore(this.storeName);
    const result = callback(store);
    return result instanceof IDBRequest ? await this._promisifyRequest(result) : await result;
  }

  /**
   * Converts IndexedDB request events into Promises, allowing for 
   * cleaner and more modern asynchronous handling. This is 
   * essential for making IndexedDB operations fit seamlessly into 
   * the Promise-based architecture of JavaScript applications.
   */
  private async _promisifyRequest<T>(request: IDBRequest): Promise<T> {
    return await new Promise((resolve, reject) => {
      request.onsuccess = () => resolve(request.result as T);
      request.onerror = () => reject(request.error);
    });
  }
}
Nach dem Login kopieren

Integration des Adapters in den Cache

Der Cache akzeptiert einen optionalen StorageAdapter. Falls bereitgestellt, initialisiert es die Datenbankverbindung, lädt Daten in den Speicher und hält den Cache und den Speicher synchron.

private constructor(capacity: number, storageAdapter?: StorageAdapter) {
  this.capacity = capacity;
  this.storageAdapter = storageAdapter;

  if (this.storageAdapter) {
    this.storageAdapter.connect().catch((error) => {
      throw new Error(error);
    });

    this.storageAdapter.getAll().then((data) => {
      for (const key in data) {
        this.put(key, data[key] as T);
      }
    }).catch((error) => {
      throw new Error(error);
    });
  }

  this.hash = new Map();
  this.head = this.tail = undefined;

  this.hitCount = this.missCount = this.evictionCount = 0;
}
Nach dem Login kopieren

Warum Adapter- und Strategiemuster?

Verwendung des Adaptermusters:

  • Entkoppeltden Cache von bestimmten Speichermechanismen.
  • Gewährleistet Erweiterbarkeit für neue Speicher-Backends.

Kombination mit dem Strategiemuster:

  • Ermöglicht die Laufzeitauswahl der Persistenzschicht.
  • Vereinfacht das Testen durch Verspotten verschiedener Adapter.

Wichtige Designpraktiken

  • Abstrakte API: Hält die Cache-Logik unabhängig von Speicherdetails.
  • Singleton-Cache: Stellt die Konsistenz gemeinsamer Zustände sicher.
  • Asynchrone Initialisierung: Vermeidet blockierende Vorgänge während der Einrichtung.
  • Lazy Loading:Lädt persistente Daten nur, wenn ein Speicheradapter bereitgestellt wird.

Nächste Schritte

Dieses Design ist robust, lässt aber Raum für Verbesserungen:

  • Optimieren Sie die Synchronisierungslogik für eine bessere Leistung.
  • Experimentieren Sie mit zusätzlichen Adaptern wie Redis oder SQLite.

Probieren Sie es aus! ?

Wenn Sie den Cache in Aktion testen möchten, ist er als npm-Paket verfügbar: adev-lru. Sie können den vollständigen Quellcode auch auf GitHub erkunden: adev-lru-Repository. Ich freue mich über alle Empfehlungen, konstruktives Feedback oder Beiträge, um es noch besser zu machen! ?

Viel Spaß beim Codieren! ?

Das obige ist der detaillierte Inhalt vonVerbesserung des LRU-Cache mit konfigurierbarer Datenpersistenz. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn

Heiße KI -Werkzeuge

Undresser.AI Undress

Undresser.AI Undress

KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover

AI Clothes Remover

Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool

Undress AI Tool

Ausziehbilder kostenlos

Clothoff.io

Clothoff.io

KI-Kleiderentferner

Video Face Swap

Video Face Swap

Tauschen Sie Gesichter in jedem Video mühelos mit unserem völlig kostenlosen KI-Gesichtstausch-Tool aus!

Heiße Werkzeuge

Notepad++7.3.1

Notepad++7.3.1

Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version

SublimeText3 chinesische Version

Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1

Senden Sie Studio 13.0.1

Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6

Dreamweaver CS6

Visuelle Webentwicklungstools

SublimeText3 Mac-Version

SublimeText3 Mac-Version

Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Heiße Themen

Java-Tutorial
1659
14
PHP-Tutorial
1258
29
C#-Tutorial
1232
24
Entmystifizieren JavaScript: Was es tut und warum es wichtig ist Entmystifizieren JavaScript: Was es tut und warum es wichtig ist Apr 09, 2025 am 12:07 AM

JavaScript ist der Eckpfeiler der modernen Webentwicklung. Zu den Hauptfunktionen gehören eine ereignisorientierte Programmierung, die Erzeugung der dynamischen Inhalte und die asynchrone Programmierung. 1) Ereignisgesteuerte Programmierung ermöglicht es Webseiten, sich dynamisch entsprechend den Benutzeroperationen zu ändern. 2) Die dynamische Inhaltsgenerierung ermöglicht die Anpassung der Seiteninhalte gemäß den Bedingungen. 3) Asynchrone Programmierung stellt sicher, dass die Benutzeroberfläche nicht blockiert ist. JavaScript wird häufig in der Webinteraktion, der einseitigen Anwendung und der serverseitigen Entwicklung verwendet, wodurch die Flexibilität der Benutzererfahrung und die plattformübergreifende Entwicklung erheblich verbessert wird.

Die Entwicklung von JavaScript: Aktuelle Trends und Zukunftsaussichten Die Entwicklung von JavaScript: Aktuelle Trends und Zukunftsaussichten Apr 10, 2025 am 09:33 AM

Zu den neuesten Trends im JavaScript gehören der Aufstieg von Typenkripten, die Popularität moderner Frameworks und Bibliotheken und die Anwendung der WebAssembly. Zukunftsaussichten umfassen leistungsfähigere Typsysteme, die Entwicklung des serverseitigen JavaScript, die Erweiterung der künstlichen Intelligenz und des maschinellen Lernens sowie das Potenzial von IoT und Edge Computing.

JavaScript -Engines: Implementierungen vergleichen JavaScript -Engines: Implementierungen vergleichen Apr 13, 2025 am 12:05 AM

Unterschiedliche JavaScript -Motoren haben unterschiedliche Auswirkungen beim Analysieren und Ausführen von JavaScript -Code, da sich die Implementierungsprinzipien und Optimierungsstrategien jeder Engine unterscheiden. 1. Lexikalanalyse: Quellcode in die lexikalische Einheit umwandeln. 2. Grammatikanalyse: Erzeugen Sie einen abstrakten Syntaxbaum. 3. Optimierung und Kompilierung: Generieren Sie den Maschinencode über den JIT -Compiler. 4. Führen Sie aus: Führen Sie den Maschinencode aus. V8 Engine optimiert durch sofortige Kompilierung und versteckte Klasse.

JavaScript: Erforschung der Vielseitigkeit einer Websprache JavaScript: Erforschung der Vielseitigkeit einer Websprache Apr 11, 2025 am 12:01 AM

JavaScript ist die Kernsprache der modernen Webentwicklung und wird für seine Vielfalt und Flexibilität häufig verwendet. 1) Front-End-Entwicklung: Erstellen Sie dynamische Webseiten und einseitige Anwendungen durch DOM-Operationen und moderne Rahmenbedingungen (wie React, Vue.js, Angular). 2) Serverseitige Entwicklung: Node.js verwendet ein nicht blockierendes E/A-Modell, um hohe Parallelitäts- und Echtzeitanwendungen zu verarbeiten. 3) Entwicklung von Mobil- und Desktop-Anwendungen: Die plattformübergreifende Entwicklung wird durch reaktnative und elektronen zur Verbesserung der Entwicklungseffizienz realisiert.

Python vs. JavaScript: Die Lernkurve und Benutzerfreundlichkeit Python vs. JavaScript: Die Lernkurve und Benutzerfreundlichkeit Apr 16, 2025 am 12:12 AM

Python eignet sich besser für Anfänger mit einer reibungslosen Lernkurve und einer kurzen Syntax. JavaScript ist für die Front-End-Entwicklung mit einer steilen Lernkurve und einer flexiblen Syntax geeignet. 1. Python-Syntax ist intuitiv und für die Entwicklung von Datenwissenschaften und Back-End-Entwicklung geeignet. 2. JavaScript ist flexibel und in Front-End- und serverseitiger Programmierung weit verbreitet.

So erstellen Sie eine SaaS-Anwendung mit mehreren Mietern mit Next.js (Frontend Integration) So erstellen Sie eine SaaS-Anwendung mit mehreren Mietern mit Next.js (Frontend Integration) Apr 11, 2025 am 08:22 AM

Dieser Artikel zeigt die Frontend -Integration mit einem Backend, das durch die Genehmigung gesichert ist und eine funktionale edtech SaaS -Anwendung unter Verwendung von Next.js. erstellt. Die Frontend erfasst Benutzerberechtigungen zur Steuerung der UI-Sichtbarkeit und stellt sicher, dass API-Anfragen die Rollenbasis einhalten

Von C/C nach JavaScript: Wie alles funktioniert Von C/C nach JavaScript: Wie alles funktioniert Apr 14, 2025 am 12:05 AM

Die Verschiebung von C/C zu JavaScript erfordert die Anpassung an dynamische Typisierung, Müllsammlung und asynchrone Programmierung. 1) C/C ist eine statisch typisierte Sprache, die eine manuelle Speicherverwaltung erfordert, während JavaScript dynamisch eingegeben und die Müllsammlung automatisch verarbeitet wird. 2) C/C muss in den Maschinencode kompiliert werden, während JavaScript eine interpretierte Sprache ist. 3) JavaScript führt Konzepte wie Verschlüsse, Prototypketten und Versprechen ein, die die Flexibilität und asynchrone Programmierfunktionen verbessern.

Erstellen einer SaaS-Anwendung mit mehreren Mietern mit Next.js (Backend Integration) Erstellen einer SaaS-Anwendung mit mehreren Mietern mit Next.js (Backend Integration) Apr 11, 2025 am 08:23 AM

Ich habe eine funktionale SaaS-Anwendung mit mehreren Mandanten (eine EdTech-App) mit Ihrem täglichen Tech-Tool erstellt und Sie können dasselbe tun. Was ist eine SaaS-Anwendung mit mehreren Mietern? Mit Multi-Tenant-SaaS-Anwendungen können Sie mehrere Kunden aus einem Sing bedienen

See all articles