Utilisation de hooks asynchrones pour la gestion du contexte de demande dans Node.js

Online Coding Courses for Kids

introduction

Crochets asynchrones sont un module principal de Node.js qui fournit une API pour suivre la durée de vie des ressources asynchrones dans une application Node. Une ressource asynchrone peut être considérée comme un objet auquel est associé un rappel.

Les exemples incluent, mais ne sont pas limités à: Promesses, Timeouts, TCPWrap, UDP etc. La liste complète des ressources asynchrones que nous pouvons suivre à l’aide de cette API peut être trouvée ici.

La fonctionnalité Async Hooks a été introduite en 2017, dans Node.js version 8 et est toujours expérimentale. Cela signifie que des modifications incompatibles avec les versions antérieures peuvent encore être apportées aux futures versions de l’API. Cela étant dit, il n’est actuellement pas jugé apte à la production.

Dans cet article, nous examinerons plus en détail les Hooks Async – ce qu’ils sont, pourquoi ils sont importants, où nous pouvons les utiliser et comment nous pouvons les exploiter pour un cas d’utilisation particulier, c’est-à-dire demander la gestion du contexte dans un nœud. js et application Express.

Que sont les hooks asynchrones?

Comme indiqué précédemment, la classe Async Hooks est un module Node.js principal qui fournit une API pour le suivi des ressources asynchrones dans votre application Node.js. Cela inclut également le suivi des ressources créées par les modules Node natifs tels que fs et net.

Pendant la durée de vie d’une ressource asynchrone, il y a 4 événements qui se déclenchent et que nous pouvons suivre, avec Async Hooks. Ceux-ci inclus:

  1. init – Appelé lors de la construction de la ressource asynchrone
  2. before – Appelé avant le rappel de la ressource
  3. after – Appelé après que le rappel de la ressource a été appelé
  4. destroy – Appelé après la destruction de la ressource asynchrone
  5. promiseResolve – Appelé quand le resolve() fonction d’une promesse est invoquée.

Vous trouverez ci-dessous un extrait de code résumé de l’API Async Hooks du présentation dans la documentation Node.js:

const async_hooks = require('async_hooks');

const exec_id = async_hooks.executionAsyncId();
const trigger_id = async_hooks.triggerAsyncId();
const asyncHook = async_hooks.createHook({
  init: function (asyncId, type, triggerAsyncId, resource) { },
  before: function (asyncId) { },
  after: function (asyncId) { },
  destroy: function (asyncId) { },
  promiseResolve: function (asyncId) { }
});
asyncHook.enable();
asyncHook.disable();

le executionAsyncId() La méthode renvoie un identifiant du contexte d’exécution actuel.

le triggerAsyncId() La méthode renvoie l’identificateur de la ressource parente qui a déclenché l’exécution de la ressource asynchrone.

le createHook() crée une instance de hook asynchrone, en prenant les événements susmentionnés comme des rappels facultatifs.

Pour permettre le suivi de nos ressources, nous appelons le enable() méthode de notre instance de hook async que nous créons avec le createHook() méthode.

Nous pouvons également désactiver le suivi en appelant le disable() fonction.

Après avoir vu ce qu’implique l’API Async Hooks, voyons pourquoi nous devrions l’utiliser.

Quand utiliser les crochets asynchrones

L’ajout d’Async Hooks à l’API principale a offert de nombreux avantages et cas d’utilisation. Certains d’entre eux comprennent:

  1. Meilleur débogage – En utilisant Async Hooks, nous pouvons améliorer et enrichir les traces de pile des fonctions asynchrones.
  2. Puissantes capacités de traçage, en particulier lorsqu’elles sont combinées avec l’API Performance de Node. De plus, comme l’API Async Hooks est native, la surcharge de performances est minime.
  3. Gestion du contexte de la requête Web – pour capturer les informations d’une requête pendant la durée de vie de cette requête, sans transmettre l’objet de requête partout. En utilisant Async Hooks, cela peut être fait n’importe où dans le code et peut être particulièrement utile lors du suivi du comportement des utilisateurs dans un serveur.

Dans cet article, nous verrons comment gérer le traçage des ID de demande à l’aide de Async Hooks dans une application Express.

Utilisation de hooks asynchrones pour la gestion du contexte de demande

Dans cette section, nous illustrerons comment nous pouvons tirer parti des Hooks Async pour effectuer un traçage simple des ID de demande dans une application Node.js.

Configuration des gestionnaires de contexte de demande

Nous commencerons par créer un répertoire dans lequel résideront nos fichiers d’application, puis nous y entrerons:

mkdir async_hooks && cd async_hooks 

Ensuite, nous devrons initialiser notre application Node.js dans ce répertoire avec npm et paramètres par défaut:

npm init -y

Cela crée un package.json fichier à la racine du répertoire.

Ensuite, nous devrons installer Express et uuid packages en tant que dépendances. Nous utiliserons le uuid package pour générer un ID unique pour chaque demande entrante.

Enfin, nous installons le esm module afin que les versions de Node.js inférieures à v14 puissent exécuter cet exemple:

npm install express uuid esm --save

Ensuite, créez un hooks.js fichier à la racine du répertoire:

touch hooks.js

Ce fichier contiendra le code qui interagit avec le async_hooks module. Il exporte deux fonctions:

  • Celui qui active un Async Hook pour une requête HTTP, en gardant une trace de son ID de requête donné et de toutes les données de requête que nous aimerions conserver.
  • L’autre renvoie les données de demande gérées par le hook en fonction de son ID de hook async.

Mettons cela dans le code:

require = require('esm')(module);
const asyncHooks = require('async_hooks');
const { v4 } = require('uuid');
const store = new Map();

const asyncHook = asyncHooks.createHook({
    init: (asyncId, _, triggerAsyncId) => {
        if (store.has(triggerAsyncId)) {
            store.set(asyncId, store.get(triggerAsyncId))
        }
    },
    destroy: (asyncId) => {
        if (store.has(asyncId)) {
            store.delete(asyncId);
        }
    }
});

asyncHook.enable();

const createRequestContext = (data, requestId = v4()) => {
    const requestInfo = { requestId, data };
    store.set(asyncHooks.executionAsyncId(), requestInfo);
    return requestInfo;
};

const getRequestContext = () => {
    return store.get(asyncHooks.executionAsyncId());
};

module.exports = { createRequestContext, getRequestContext };

Dans ce morceau de code, nous avons d’abord besoin du esm module pour fournir une compatibilité descendante pour les versions Node qui n’ont pas de support natif pour les exportations de modules expérimentaux. Cette fonctionnalité est utilisée en interne par le uuid module.

Ensuite, nous avons également besoin du async_hooks et uuid modules. Du uuid module, on déstructure le v4 que nous utiliserons plus tard pour générer des UUID version 4.

Ensuite, nous créons un magasin qui mappera chaque ressource asynchrone à son contexte de demande. Pour cela, nous utilisons une simple carte JavaScript.

Ensuite, nous appelons le createHook() méthode de la async_hooks module et implémentez le init() et destroy() rappels. Dans la mise en œuvre de notre init() rappel, nous vérifions si le triggerAsyncId est présent dans le magasin.

S’il existe, nous créons un mappage du asyncId aux données de demande stockées sous le triggerAsyncId. Cela garantit en effet que nous stockons le même objet de requête pour les ressources asynchrones enfants.

le destroy() callback vérifie si le magasin a le asyncId de la ressource et la supprime si elle est vraie.

Pour utiliser notre hook, nous l’activons en appelant le enable() méthode de la asyncHook instance que nous avons créée.

Ensuite, nous créons 2 fonctions – createRequestContext() et getRequestContext que nous utilisons pour créer et obtenir notre contexte de demande respectivement.

le createRequestContext() La fonction reçoit les données de la demande et un ID unique en tant qu’arguments. Il crée alors un requestInfo objet des arguments et tente de mettre à jour le magasin avec l’ID asynchrone du contexte d’exécution actuel comme clé, et le requestInfo comme valeur.

le getRequestContext() La fonction, d’autre part, vérifie si le magasin contient un ID correspondant à l’ID du contexte d’exécution courant.

Nous exportons enfin les deux fonctions en utilisant le module.exports() syntaxe.

Nous avons configuré avec succès notre fonctionnalité de gestion du contexte de demande. Procédons à la configuration de notre Express serveur qui recevra les demandes.

Configuration du serveur Express

Après avoir mis en place notre contexte, nous allons maintenant procéder à la création de notre Express serveur afin que nous puissions capturer les requêtes HTTP. Pour ce faire, créez un server.js fichier à la racine du répertoire comme suit:

touch server.js

Notre serveur acceptera une requête HTTP sur le port 3000. Il crée un Hook Async pour suivre chaque requête en appelant le createRequestContext() dans un middleware function – une fonction qui a accès aux objets de requête et de réponse HTTP. Le serveur envoie ensuite une réponse JSON avec les données capturées par le Hook Async.

À l’intérieur de server.js fichier, entrez le code suivant:

const express = require('express');
const ah = require('./hooks');
const app = express();
const port = 3000;

app.use((request, response, next) => {
    const data = { headers: request.headers };
    ah.createRequestContext(data);
    next();
});

const requestHandler = (request, response, next) => {
    const reqContext = ah.getRequestContext();
    response.json(reqContext);
    next()
};

app.get("https://stackabuse.com/", requestHandler)

app.listen(port, (err) => {
    if (err) {
        return console.error(err);
    }
    console.log(`server is listening on ${port}`);
});

Dans ce morceau de code, nous avons besoin express et notre hooks modules comme dépendances. Nous créons ensuite un Express app en appelant le express() fonction.

Ensuite, nous avons mis en place un middleware qui déstructure les en-têtes de requête en les sauvegardant dans une variable appelée data. Il appelle ensuite le createRequestContext() fonction passant data comme argument. Cela garantit que les en-têtes de la demande seront conservés tout au long du cycle de vie de la demande avec le Hook Async.

Enfin, nous appelons le next() pour accéder au middleware suivant dans notre pipeline middleware ou appeler le prochain gestionnaire de route.

Après notre middleware, nous écrivons le requestHandler() fonction qui gère un GET requête sur le domaine racine du serveur. Vous remarquerez que dans cette fonction, nous pouvons avoir accès à notre contexte de demande via le getRequestContext() fonction. Cette fonction renvoie un objet représentant les en-têtes de demande et l’ID de demande générés et stockés dans le contexte de la demande.

Nous créons ensuite un point de terminaison simple et attachons notre gestionnaire de requêtes en tant que rappel.

Enfin, nous faisons écouter à notre serveur les connexions sur le port 3000 en appelant le listen() méthode de notre instance d’application.

Avant d’exécuter le code, ouvrez le package.json à la racine du répertoire et remplacez le test section du script avec ceci:

"start": "node server.js"

Ceci fait, nous pouvons exécuter notre application avec la commande suivante:

npm start

Vous devriez recevoir une réponse sur votre terminal indiquant que l’application s’exécute sur le port 3000, comme indiqué:

> [email protected] start /Users/allanmogusu/StackAbuse/async-hooks-demo
> node server.js

(node:88410) ExperimentalWarning: Conditional exports is an experimental feature. This feature could change at any time
server is listening on 3000

Avec notre application en cours d’exécution, ouvrez une instance de terminal distincte et exécutez ce qui suit curl commande pour tester notre route par défaut:

curl http://localhost:3000

Ce curl commande fait un GET demande à notre itinéraire par défaut. Vous devriez obtenir une réponse similaire à celle-ci:

$ curl http://localhost:3000
{"requestId":"3aad88a6-07bb-41e0-ab5a-fa9d5c0269a7","data":{"headers":{"host":"localhost:3000","user-agent":"curl/7.64.1","accept":"*/*"}}}%

Notez que le généré requestId et nos en-têtes de demande sont renvoyés. La répétition de la commande devrait générer un nouvel ID de demande puisque nous allons faire une nouvelle demande:

$ curl http://localhost:3000
{"requestId":"38da84792-e782-47dc-92b4-691f4285b172","data":{"headers":{"host":"localhost:3000","user-agent":"curl/7.64.1","accept":"*/*"}}}%

La réponse contient l’ID que nous avons généré pour la requête et les en-têtes que nous avons capturés dans la fonction middleware. Avec Async Hooks, nous pourrions facilement passer des données d’un middleware à un autre pour la même requête.

Conclusion

Async Hooks fournit une API pour suivre la durée de vie des ressources asynchrones dans une application Node.js.

Dans cet article, nous avons brièvement examiné l’API Async Hooks, les fonctionnalités qu’elle fournit et comment nous pouvons en tirer parti. Nous avons spécifiquement couvert un exemple de base de la façon dont nous pouvons utiliser Async Hooks pour gérer efficacement et proprement le contexte des requêtes Web.

Cependant, depuis la version 14 de Node.js, l’API Async Hooks est livrée avec un stockage local asynchrone, une API qui facilite la gestion du contexte de demande dans Node.js. Vous pouvez en savoir plus ici. En outre, le code de ce didacticiel est accessible ici.


Close Menu