Maison > interface Web > js tutoriel > Le stockage local asynchrone est là pour vous aider

Le stockage local asynchrone est là pour vous aider

Mary-Kate Olsen
Libérer: 2024-12-22 22:22:14
original
659 Les gens l'ont consulté

Lorsque vous entendez l'expression « Stockage local asynchrone », qu'est-ce qui vous vient à l'esprit ? Au début, vous pourriez penser qu’il s’agit d’une implémentation magique du stockage local basé sur un navigateur. Cependant, cette hypothèse est incorrecte. Le stockage local asynchrone n'est ni lié au navigateur ni un mécanisme de stockage typique. Il est probable qu'une ou deux bibliothèques que vous avez utilisées l'utilisent sous le capot. Dans de nombreux cas, cette fonctionnalité peut vous éviter de gérer un code compliqué.

Qu’est-ce que le stockage local asynchrone ?

Async Local Storage est une fonctionnalité introduite dans Node.js, initialement ajoutée dans les versions v13.10.0 et v12.17.0, puis stabilisée dans la v16.4.0. Il fait partie du module async_hooks, qui fournit un moyen de suivre les ressources asynchrones dans les applications Node.js. La fonctionnalité permet la création d'un contexte partagé auquel plusieurs fonctions asynchrones peuvent accéder sans le transmettre explicitement. Le contexte est disponible dans chaque (et unique) opération exécutée dans le rappel transmis à la méthode run() de l'instance AsyncLocalStorage.

Un modèle pour utiliser AsyncLocalStorage

Avant de plonger dans les exemples, expliquons le modèle que nous allons utiliser.

Initialisation

import { AsyncLocalStorage } from "async_hooks";
import { Context } from "./types";

export const asyncLocalStorage = new AsyncLocalStorage<Context>();

// export const authAsyncLocalStorage = new AuthAsyncLocalStorage<AuthContext>()
Copier après la connexion
Copier après la connexion
Copier après la connexion

Dans le module ci-dessus, nous initialisons une instance de AsyncLocalStorage et l'exportons sous forme de variable.

Utilisation

asyncLocalStorage.run({ userId }, async () => {
  const usersData: UserData = await collectUsersData();
  console.log("usersData", usersData);
});

// (method) AsyncLocalStorage<unknown>.run<Promise<void>>(store: unknown, callback: () => Promise<void>): Promise<void> (+1 overload)
Copier après la connexion
Copier après la connexion
Copier après la connexion

La méthode run() prend deux arguments : storage, qui contient les données que nous souhaitons partager, et callback, où nous plaçons notre logique. En conséquence, le stockage devient accessible à chaque appel de fonction dans le rappel, permettant un partage transparent des données entre les opérations asynchrones.

async function collectUsersData() {
  const context = asyncLocalStorage.getStore();
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

Pour accéder au contexte, nous importons notre instance et appelons la méthode asyncLocalStorage.getStore(). Ce qui est bien, c'est que le stockage récupéré depuis getStore() est typé car nous avons transmis le type Context à AsyncLocalStorage lors de l'initialisation : new AsyncLocalStorage().

Stockage local asynchrone comme contexte d'authentification

Il n'y a pas d'application web sans système d'authentification. Nous devons valider les jetons d'authentification et extraire les informations utilisateur. Une fois que nous avons obtenu l'identité de l'utilisateur, nous souhaitons la rendre disponible dans les gestionnaires de routes et éviter de dupliquer le code dans chacun d'eux. Voyons comment nous pouvons utiliser AsyncLocalStorage pour implémenter un contexte d'authentification tout en gardant notre code propre.

J'ai choisi fastify pour cet exemple.

Selon la documentation, fastify est :

Framework Web rapide et faible, pour Node.js

Ok, commençons :

  1. Installer fastify :
import { AsyncLocalStorage } from "async_hooks";
import { Context } from "./types";

export const asyncLocalStorage = new AsyncLocalStorage<Context>();

// export const authAsyncLocalStorage = new AuthAsyncLocalStorage<AuthContext>()
Copier après la connexion
Copier après la connexion
Copier après la connexion
  1. Définir le type pour notre contexte d'authentification :
asyncLocalStorage.run({ userId }, async () => {
  const usersData: UserData = await collectUsersData();
  console.log("usersData", usersData);
});

// (method) AsyncLocalStorage<unknown>.run<Promise<void>>(store: unknown, callback: () => Promise<void>): Promise<void> (+1 overload)
Copier après la connexion
Copier après la connexion
Copier après la connexion
  1. Initialisez une instance de AsyncLocalStorage, attribuez-la à une variable et exportez la variable. N'oubliez pas de transmettre le type approprié : new AsyncLocalStorage().
async function collectUsersData() {
  const context = asyncLocalStorage.getStore();
}
Copier après la connexion
Copier après la connexion
Copier après la connexion
  1. Initialisez une instance Fastify et ajoutez un utilitaire de gestion des erreurs :
npm install fastify
Copier après la connexion
Copier après la connexion

Vient maintenant la partie très importante. Nous allons ajouter un hook onRequest pour envelopper les gestionnaires avec la méthode authAsyncLocalStorage.run().

type Context = Map<"userId", string>;
Copier après la connexion
Copier après la connexion

Après une validation réussie, nous appelons la méthode run() depuis notre authAsyncLocalStorage. Comme argument de stockage, nous transmettons le contexte d'authentification avec l'ID utilisateur récupéré du jeton. Dans le rappel, nous appelons la fonction done pour continuer le cycle de vie Fastify.

Si nous avons des contrôles d'authentification qui nécessitent des opérations asynchrones, nous devons les ajouter au rappel. En effet, selon la documentation :

le rappel terminé n'est pas disponible lors de l'utilisation de async/await ou du renvoi d'une promesse. Si vous invoquez un rappel effectué dans cette situation, un comportement inattendu peut se produire, par ex. appel en double des gestionnaires

Voici un exemple de ce à quoi cela pourrait ressembler :

import { AsyncLocalStorage } from "async_hooks";
import { Context } from "./types";

export const authAsyncLocalStorage = new AsyncLocalStorage<Context>();
Copier après la connexion
Copier après la connexion

Notre exemple ne comporte qu'un seul itinéraire protégé. Dans des scénarios plus complexes, vous devrez peut-être encapsuler uniquement des routes spécifiques avec le contexte d'authentification. Dans de tels cas, vous pouvez soit :

  1. Enveloppez le hook onRequest dans un plugin personnalisé qui s'applique uniquement à des routes spécifiques.
  2. Ajoutez une logique de distinction de route dans le hook onRequest lui-même.

Très bien, notre contexte est posé et nous pouvons désormais définir un itinéraire protégé :

import Fastify from "fastify";

/* other code... */

const app = Fastify();

function sendUnauthorized(reply: FastifyReply, message: string) {
  reply.code(401).send({ error: `Unauthorized: ${message}` });
}

/* other code... */
Copier après la connexion
Copier après la connexion

Le code est assez simple. Nous importons authAsyncLocalStorage, récupérons l'userId, initialisons UserRepository et récupérons les données. Cette approche permet au gestionnaire d'itinéraire de rester propre et concentré.

Explorer comment Next.js utilise le stockage local asynchrone

Dans cet exemple, nous allons réimplémenter l'assistant de cookies de Next.js. Mais attendez, c'est un article sur AsyncLocalStorage, n'est-ce pas ? Alors pourquoi parle-t-on de cookies ? La réponse est simple : Next.js utilise AsyncLocalStorage pour gérer les cookies sur le serveur. C'est pourquoi lire un cookie dans un composant serveur est aussi simple que :

import Fastify from "fastify";
import { authAsyncLocalStorage } from "./context";
import { getUserIdFromToken, validateToken } from "./utils";

/* other code... */

app.addHook(
  "onRequest",
  (request: FastifyRequest, reply: FastifyReply, done: () => void) => {
    const accessToken = request.headers.authorization?.split(" ")[1];
    const isTokenValid = validateToken(accessToken);
    if (!isTokenValid) {
      sendUnauthorized(reply, "Access token is invalid");
    }
    const userId = accessToken ? getUserIdFromToken(accessToken) : null;

    if (!userId) {
      sendUnauthorized(reply, "Invalid or expired token");
    }
    authAsyncLocalStorage.run(new Map([["userId", userId]]), async () => {
      await new Promise((resolve) => setTimeout(resolve, 2000));
      sendUnauthorized(reply, "Invalid or expired token");
      done();
    });
  },
);

/* other code... */
Copier après la connexion

Nous utilisons la fonction cookies exportée depuis next/headers, qui propose plusieurs méthodes de gestion des cookies. Mais comment est-ce techniquement possible ?

Il est maintenant temps de commencer notre réimplémentation"

Tout d'abord, je tiens à mentionner que cet exemple est basé sur les connaissances que j'ai acquises grâce à une superbe vidéo, de Lee Robinson et en plongeant dans le référentiel Next.js.

Dans cet exemple, nous utiliserons Hono comme framework de serveur. Je l'ai choisi pour deux raisons :

  1. Je voulais juste essayer.
  2. Il offre un support solide pour JSX.

Première installation de Hono :

import { AsyncLocalStorage } from "async_hooks";
import { Context } from "./types";

export const asyncLocalStorage = new AsyncLocalStorage<Context>();

// export const authAsyncLocalStorage = new AuthAsyncLocalStorage<AuthContext>()
Copier après la connexion
Copier après la connexion
Copier après la connexion

Maintenant, initialisez Hono et ajoutez un middleware :

asyncLocalStorage.run({ userId }, async () => {
  const usersData: UserData = await collectUsersData();
  console.log("usersData", usersData);
});

// (method) AsyncLocalStorage<unknown>.run<Promise<void>>(store: unknown, callback: () => Promise<void>): Promise<void> (+1 overload)
Copier après la connexion
Copier après la connexion
Copier après la connexion

Le code ressemble au middleware de l'exemple Fastify, n'est-ce pas ? Pour définir le contexte, nous utilisons setCookieContext, qui est importé du module cookies - notre implémentation simple et personnalisée de la fonction cookies. Suivons la fonction setCookieContext et naviguons jusqu'au module à partir duquel elle a été importée :

async function collectUsersData() {
  const context = asyncLocalStorage.getStore();
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

La fonction setCookieContext (dont nous avons transmis la valeur de retour à cookieAsyncLocalStorage.run() dans le middleware Hono) extrait les cookies du paramètre c, qui représente le contexte hono, et les regroupe avec des fermetures qui fournissent des fonctions utilitaires pour gérer les cookies.

Notre fonction cookies reproduit la fonctionnalité des cookies de next/headers. Il utilise la méthode cookieAsyncLocalStorage.getStore() pour accéder au même contexte qui est transmis à cookieAsyncLocalStorage.run() lorsqu'il est appelé.

Nous avons enveloppé le retour de notre fonction cookies dans une promesse d'imiter le comportement de l'implémentation Next.js. Avant la version 15, cette fonction était synchrone. Désormais, dans le code Next.js actuel, les méthodes renvoyées par les cookies sont attachées à un objet de promesse, comme le montre l'exemple simplifié suivant :

npm install fastify
Copier après la connexion
Copier après la connexion

Un autre point à mentionner est que dans notre cas, l'utilisation de cookies.setCookie et cookies.deleteCookie génère toujours une erreur, similaire au comportement observé dans Next.js lors de la définition de cookies dans un composant serveur. Nous avons codé en dur cette logique car, dans l'implémentation d'origine, la possibilité d'utiliser setCookie ou deleteCookie dépend de la propriété phase(WorkUnitPhase) stockée dans le stockage appelé RequestStore (il s'agit de l'implémentation d'AsyncLocalStorage et stocke également les cookies). Cependant, ce sujet conviendrait mieux à un autre article. Pour garder cet exemple simple, omettons la simulation de WorkUnitPhase.

Nous devons maintenant ajouter notre code React.

  1. Ajoutez le composant App :
type Context = Map<"userId", string>;
Copier après la connexion
Copier après la connexion
  1. Ajouter un composant de gestion des cookies :
import { AsyncLocalStorage } from "async_hooks";
import { Context } from "./types";

export const authAsyncLocalStorage = new AsyncLocalStorage<Context>();
Copier après la connexion
Copier après la connexion

L'utilisation des cookies est similaire à celle utilisée dans les composants du serveur Next.js React.

  1. Ajoutez un gestionnaire de route pour afficher le modèle :
import Fastify from "fastify";

/* other code... */

const app = Fastify();

function sendUnauthorized(reply: FastifyReply, message: string) {
  reply.code(401).send({ error: `Unauthorized: ${message}` });
}

/* other code... */
Copier après la connexion
Copier après la connexion

Notre modèle est rendu par la méthode html à partir du contexte hono. Le point clé ici est que le gestionnaire de route s'exécute dans la méthode asyncLocalStorage.run(), qui prend cookieContext. De ce fait, nous pouvons accéder à ce contexte dans le composant DisplayCookies via la fonction cookies.

Il n'est pas possible de définir des cookies dans les composants du serveur React, nous devons donc le faire manuellement :

Async Local Storage is Here to Help You

Actualisons une page :

Async Local Storage is Here to Help You

Et voilà, nos cookies sont récupérés et affichés avec succès.

Conclusions

Il existe de nombreux autres cas d'utilisation pour asyncLocalStorage. Cette fonctionnalité vous permet de créer des contextes personnalisés dans presque n'importe quelle infrastructure de serveur. Le contexte asyncLocalStorage est encapsulé dans l'exécution de la méthode run(), ce qui facilite sa gestion. Il est parfait pour gérer des scénarios basés sur des requêtes. L'API est simple et flexible, permettant l'évolutivité en créant des instances pour chaque état. Il est possible de gérer de manière transparente des contextes séparés pour des éléments tels que l'authentification, la journalisation et les indicateurs de fonctionnalités.

Malgré ses avantages, il y a quelques considérations à garder à l’esprit. J'ai entendu des opinions selon lesquelles asyncLocalStorage introduit trop de « magie » dans le code. J'avoue que lorsque j'ai utilisé cette fonctionnalité pour la première fois, il m'a fallu un certain temps pour bien comprendre le concept. Une autre chose à considérer est que l’importation du contexte dans un module crée une nouvelle dépendance que vous devrez gérer. Cependant, en fin de compte, transmettre des valeurs via des appels de fonctions profondément imbriqués est bien pire.

Merci d'avoir lu et à bientôt dans le prochain post !?

PS : Vous pouvez retrouver les exemples (plus un bonus) ici

Source du message Bog : https://www.aboutjs.dev/en/async-local-storage-is-here-to-help-you

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal