Ajouter un callback à une requête Ajax

Attention, on parle ici d’ajouter des fonctions de rappel à des requêtes Ajax lancées par des librairies tierces, donc des requêtes dont on ne maîtrise rien :

  • on se sait pas quand elles sont exécutées
  • on ne sait pas quand la réponse arrive
  • on ne connait pas le status de la réponse
  • bref on se sait pas grand chose…

Basique implantation

Allons y franchement, je colle tout de suite la fonction qui va nous permettre d’ajouter nos fonctions de rappel :

function addXMLRequestCallback(callback) {
    var nativeSend, i;
    if(XMLHttpRequest.callbacks) {
        // We've already overridden send() so just add the callback
        XMLHttpRequest.callbacks.push(callback);
    } else {
        // Create a callback queue
        XMLHttpRequest.callbacks = [callback];
        // Store the native send()
        nativeSend = XMLHttpRequest.prototype.send;
        // Override the native send()
        XMLHttpRequest.prototype.send = function() {
            // Process the callback queue
            // The xhr instance is passed into each callback
            for (i = 0; i < XMLHttpRequest.callbacks.length; i++) {
                XMLHttpRequest.callbacks[i](this);
            }
            // Call the native send()
            nativeSend.apply(this, arguments);
        }
    }
}

Placez cette fonction dans le scope global ou dans une closure, l’essentiel est qu’elle soit accessible quand on en a besoin. Explication : à chaque requête Ajax on a maintenant la possibilité de modifier l’objet XMLHttpRequest ce qui est juste génial. Voici un exemple simple d’utilisation de cette fonction (on fait simple, on affiche juste un message dans la console) :

addXMLRequestCallback(function(xhr) {
    var oldonreadystatechange = xhr.onreadystatechange || null;
    xhr.onreadystatechange = function() {
        if(xhr.readyState === XMLHttpRequest.DONE) {
            if(xhr.status === 200) {
                // Stuff to execute here
                console.log(xhr.responseText);
            }
        }

        // Call native function if exists
        if(oldonreadystatechange) {
            oldonreadystatechange.apply(xhr, arguments);
        }
    }
});

On en profite pour sauvegarder la propriété onreadystatechange pour l’exécuter juste après mais ce n’est pas indispensable.

Maintenant, voilà un exemple complet :

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = function(event) {
    // XMLHttpRequest.DONE === 4
    if (this.readyState === XMLHttpRequest.DONE) {
        if (this.status === 200) {
            console.log("Réponse reçue: %s", this.responseText);
        } else {
            console.log("Status de la réponse: %d (%s)", this.status, this.statusText);
        }
    }
};

addXMLRequestCallback(function(xhr) {
    var oldonreadystatechange = xhr.onreadystatechange || null;
    xhr.onreadystatechange = function() {
        if(xhr.readyState === XMLHttpRequest.DONE) {
            if(xhr.status === 200) {
                // Stuff to execute here
                console.log(xhr.responseText);
            }
        }

        // Call native function if exists
        if(oldonreadystatechange) {
            oldonreadystatechange.apply(xhr, arguments);
        }
    }
});

xhr.open('GET', 'https://httpbin.org/get', true);
xhr.send(null);

function addXMLRequestCallback(callback) {
    var nativeSend, i;
    if(XMLHttpRequest.callbacks) {
        // We've already overridden send() so just add the callback
        XMLHttpRequest.callbacks.push(callback);
    } else {
        // Create a callback queue
        XMLHttpRequest.callbacks = [callback];
        // Store the native send()
        nativeSend = XMLHttpRequest.prototype.send;
        // Override the native send()
        XMLHttpRequest.prototype.send = function() {
            // Process the callback queue
            // The xhr instance is passed into each callback
            for (i = 0; i < XMLHttpRequest.callbacks.length; i++) {
                XMLHttpRequest.callbacks[i](this);
            }
            // Call the native send()
            nativeSend.apply(this, arguments);
        }
    }
}

Cette implantation est parfaitement fonctionnelle, elle permet, par exemple, d’effectuer des actions lorsque Prestashop met à jour son panier, modifie une déclinaison, etc.

Références externes