Tutoriel Vidéo JavaScript Debounce & Throttle

Télécharger la vidéo

Je vous propose de découvrir aujourd'hui 2 fonctions plutôt utiles en JavaScript : la fonction debounce et throttle. Ces 2 fonctions permettent d'introduire une notion de délai lors de l'appel de fonction et ainsi limiter les appels inutiles à un code pourrait venir alourdir votre application.

debounce()

La fonction debounce permet de déclencher l'appel à une fonction après un certain délai (un peu comme la fonction setTimeout()) mais permet en plus de réinitialiser le timer si on demande une nouvelle exécution dans un intervalle de temps plus court que le délai. Par exemple, on peut écouter la frappe d'un utilisateur dans un champ texte, mais ne pas vouloir appeler notre callback seulement si l'utilisateur marque une pause suffisamment longue.

search.addEventListener('keyup', function(e){
    // le code ici sera appelé à chaque pression de touche
})

La fonction debounce va retourner une fonction "sans rebond" qui ne sera exécutée seulement si une pause suffisamment longue a été marquée.

search.addEventListener('keyup', debounce(function(e){
    // Le code ici sera exécuté au bout de 350 ms 
    // mais si l'utilisateur tape une nouvelle fois dans cet intervalle de temps, le timer sera réinitialisé
}, 350))

Et voilà un exemple de code pour cette fonction

function debounce(callback, delay){
    var timer;
    return function(){
        var args = arguments;
        var context = this;
        clearTimeout(timer);
        timer = setTimeout(function(){
            callback.apply(context, args);
        }, delay)
    }
}

On utilise ici un timer qui permet de mettre le délai pour l'appel à notre fonction. En plus de ça on appelle le callback en utilisant la méthode apply pour "transférer" le contexte et les paramètres à notre callback.

throttle()

La fonction throttle permet d'éviter des appels consécutifs en introduisant un délai. Elle servira surtout lorsque l'on écoutera des évènements pouvant se produire un très grand nombre de fois dans un intervalle de temps très court (scroll, resize, mouseMove...).

window.addEventListener('scroll', function(e){
    // le code ici sera appelé à chaque fois que l'utilisateur bouge la scrollbar
})

Le problème de cet évènement c'est qu'il peut se lancer plusieurs centaines de fois pendant un simple mouvement de scrollbar. Si notre code est un peu lourd, cela peut entrainer des lags et rendre le scroll peu fluide.

window.addEventListener('scroll', throttle(function(e){
    // Le code ici ne pourra être exécuté que toutes les 50 ms (20 appels max par secondes)
}, 50))

Et voilà un exemple de code pour cette fonction

function throttle(callback, delay) {
    var last;
    var timer;
    return function () {
        var context = this;
        var now = +new Date();
        var args = arguments;
        if (last && now < last + delay) {
            // le délai n'est pas écoulé on reset le timer
            clearTimeout(timer);
            timer = setTimeout(function () {
                last = now;
                callback.apply(context, args);
            }, delay);
        } else {
            last = now;
            callback.apply(context, args);
        }
    };
}

Ici on utilise en partie le code de notre fonction debounce(), mais on introduit en plus un appel tous les XXX ms en comparant le temps actuel (en ms) avec le temps du dernier appel. La partie prise du debounce nous permet de gérer le dernier appel à notre callback.

Underscore & Lodash

Ces 2 fonctions sont très utilisées et comme vous le voyez il est très facile de se créer un code qui permet d'obtenir le comportement souhaité. Ceci étant dit, il est important de noter que l'on retrouve des implémentations de throttle et debounce dans de nombreuses librairies Javascripts comme Underscorejs et Lodash. Dans le cas de ces librairies il est possible d'appeler debounce et throttle de la manière suivante :

window.addEventListener('scroll', _.throttle(function(e){

}, 40));

search.addEventListener('keyup', _.debounce(function(e){

}, 400));

Enfin si vous utilisez browserify et que vous souhaitez utiliser le système d'import, vous pouvez utiliser lodash qui est entièrement compatible avec npm

var debounce = require('lodash/function/debounce')

search.addEventListener('keyup', debounce(function(e){

}, 400));

Ces 2 librairies proposent des options supplémentaires comme la possibilité de gérer le premier appel au callback, je vous laisse lire la documentation pour voir les spécificités de chacune.