Il est assez courant d'avoir besoin de s'assurer qu'une requête HTTP ne puisse pas être effectuée plusieurs fois en parallèle.

Prenons par exemple le scénario d'expiration d'un jeton OAuth. Je navigue sur la page de détail d'un produit, cette page provoquant trois appels HTTP (une pour les informations du produit, une pour les commentaires et une dernière pour des suggestions d'autres produits). Au moment de cette navigation, ma session expire. Ces trois requêtes vont générer des 401 (Unauthorized) qui déclencheront un processus de refresh de jeton (via un interceptor Angular par exemple). Le problème ici est que si je ne prévois pas un mécanisme de blocage d'appels, trois requêtes HTTP de refresh de jeton seront effectués (donc en fonction de l'implémentation du refresh, je peux me retrouver avec des erreurs en plus de la surcharge réseau).

Cette problématique n'est pas très compliquée à régler lorsque l'on utilise des Observables, le seul problème est qu'elle rajoute du code technique un peu lourd à l'intérieur du code métier. Pour régler cela, j'ai créé un décorateur que j'utilise assez fréquemment sur mes projets et que je vous partage aujourd'hui.

Si vous souhaitez avoir plus d'informations sur les décorateurs (syntaxe, cas d'usages, etc.), vous pouvez vous réferrer à un de mes précédents articles : A la découverte des décorateurs TypeScript.

Le décorateur

Voici le code du décorateur :

import { Observable } from 'rxjs;
import { finalize, share } from 'rxjs/operators';

export function ReusePendingObservable() {
    return function (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<any>) {
        const originalMethod = descriptor.value;
        const pendingObservablePropertyName = `__pendingObservable${propertyKey}`;

        target[pendingObservablePropertyName] = null;
        descriptor.value = function (...args: any[]) {
            if (!target[pendingObservablePropertyName]) {
                const result: Observable<any> = originalMethod.apply(this, args);
                target[pendingObservablePropertyName] = result.pipe(finalize(() => target[pendingObservablePropertyName] = null), share());
            }

            return target[pendingObservablePropertyName];
        };
    };
}

L'idée est relativement simple. Une propriété nommée __pendingObservable<nom de la méthode> est créée sur le prototype de la classe afin de contenir l'observable courante. La méthode décorée est ensuite surchargée afin de stocker l'observable créée dans la variable précédente. On en profite également pour nettoyer cette variable lorsque l'observable est complete (via l'opérateur finalize) et appliquer un share sur l'observable pour éviter que chaque souscription déclenche un appel HTTP.

Son utilisation se fait de la manière suivante :

@ReusePendingObservable()
renewToken(refresh: string) : Observable<Token> {
    return this.httpClient.post([…]);
}

Il devient alors impossible d'effectuer plusieurs appels HTTP simultanées en appelant cette méthode et le code n'est pas pollué par toutes ces considérations techniques.

Bon requêtages !

Si cet article t'a plu, n'hésites pas à partager , et si tu as des questions / remarques, n'hésites pas à me contacter