Optimisez vos boucles Apps Script : Le secret pour éviter le blocage avec les grands jeux de données

Lorsqu’on débute avec Google Apps Script pour automatiser des tâches, notamment avec Google Sheets, on utilise souvent des boucles (for, forEach, etc.). C’est une approche naturelle, mais elle peut vite poser problème quand on manipule beaucoup de données. Une boucle classique peut ralentir, voire bloquer complètement votre script.

Cet article explique pourquoi les boucles traditionnelles sont inefficaces avec de gros volumes de données et comment utiliser une méthode bien plus rapide : les tableaux (Arrays).

Le piège des boucles traditionnelles : accès cellule par cellule

L’approche la plus intuitive, souvent vue dans les tutoriels, consiste à parcourir chaque ligne de la feuille de calcul et, pour chaque ligne, à lire la valeur de chaque cellule nécessaire, une par une.

Voici un exemple typique (ancienne syntaxe) :

// --- ANCIENNE MÉTHODE (Moins efficace) ---
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("NomDeVotreFeuille");
var lastRow = sheet.getLastRow();

for (var i = 2; i <= lastRow; i++) { // Boucle à partir de la ligne 2
  var agent = sheet.getRange(i, 3).getValue(); // Accès cellule par cellule
  var secteur = sheet.getRange(i, 5).getValue(); // Accès cellule par cellule
  var facturation = sheet.getRange(i, 6).getValue(); // Accès cellule par cellule

  // Condition et traitement
  if (sheet.getRange(i, 7).getValue() != 4) {
    // ... Traitement ...
  }
}

Pourquoi est-ce un problème ?

Cette méthode est facile à comprendre, car elle ressemble à la façon dont on regarde une feuille de calcul (cellule après cellule). Le souci majeur est que chaque appel à sheet.getRange(…).getValue() est une demande séparée envoyée aux serveurs de Google.

Imaginez que vous ayez des milliers, voire des dizaines de milliers de lignes. Cela signifie des milliers de demandes ! Ces allers-retours constants prennent beaucoup de temps.

Google Apps Script limite le temps d’exécution d’un script (généralement autour de 6 minutes). Si votre script prend plus de temps, il est simplement arrêté. Avec des milliers de demandes getValue(), ou si vous effectuez des actions longues dans la boucle (comme créer un PDF ou envoyer un email), vous atteindrez cette limite très rapidement.

La meilleure pratique : récupérer les données en bloc avec les tableaux (Arrays)

Pour résoudre ce problème et rendre vos scripts beaucoup plus rapides, la meilleure pratique est de lire toutes les données nécessaires en une seule fois et de les stocker dans un tableau (Array).

Au lieu de faire des milliers de petites demandes, vous faites une seule grosse demande pour obtenir toutes les données. Ensuite, vous travaillez sur ces données directement en mémoire dans votre script, ce qui est extrêmement rapide.

Comprendre les tableaux (Arrays) en Apps Script

Un tableau est une variable spéciale qui peut contenir une collection d’éléments (des nombres, du texte, etc.).

  • Tableau simple (1D) : Comme une liste ou une seule colonne. Les éléments sont accessibles par un indice qui commence à 0. monTableau[0] est le premier élément, monTableau[1] le deuxième, etc.
  • Tableau à deux dimensions (2D) : Comme une grille ou un tableau Excel. C’est ce que vous obtenez quand vous lisez plusieurs lignes et colonnes avec getValues(). C’est un tableau de lignes, où chaque ligne est elle-même un tableau de cellules. Pour accéder à une cellule, il faut donner l’indice de la ligne (à partir de 0) puis l’indice de la colonne (à partir de 0). monTableau2D[0][0] est la cellule en haut à gauche (première ligne, première colonne de la plage lue).

Mise en pratique

Voici comment appliquer cette technique avec la syntaxe JavaScript moderne :

  1. Définir la plage et récupérer les valeurs en bloc : Utilisez sheet.getRange(ligneDebut, colDebut, nbLignes, nbColonnes).getValues().
  • ligneDebut : Première ligne à lire (ex: 2 pour sauter l’en-tête).
  • colDebut : Première colonne à lire (ex: 1 pour la colonne A).
  • nbLignes : Combien de lignes lire. Souvent sheet.getLastRow() – ligneDebut + 1.
  • nbColonnes : Combien de colonnes lire. Souvent sheet.getLastColumn() – colDebut + 1.
  • getValues() : La fonction clé qui lit tout et renvoie un tableau 2D.
// --- NOUVELLE MÉTHODE (Beaucoup plus efficace avec ES6+) ---
const ss = SpreadsheetApp.getActiveSpreadsheet(); // Récupère le classeur actif
const sheet = ss.getSheetByName("dataset"); // Récupère la feuille nommée "dataset"

// Vérifie si la feuille existe et a des données
if (!sheet || sheet.getLastRow() < 2) {
  Logger.log("La feuille 'dataset' est vide ou n'existe pas, ou ne contient pas de données après l'en-tête.");
  return; // Arrête le script si pas de données à traiter
}

const ligneDebut = 2; // Commence à la ligne 2 (après l'en-tête)
const colDebut = 1; // Commence à la colonne 1 (A)
const nbLignes = sheet.getLastRow() - ligneDebut + 1; // Nombre de lignes de données
const nbColonnes = sheet.getLastColumn(); // Nombre total de colonnes dans la feuille

// Récupère TOUTES les données (sauf l'en-tête) en UNE SEULE fois
const values = sheet.getRange(ligneDebut, colDebut, nbLignes, nbColonnes).getValues();

Filtrer les données (Optionnel mais très efficace) :

Avant de boucler, utilisez la méthode filter() sur votre tableau values pour ne garder que les lignes qui vous intéressent. Cela réduit le travail à faire dans la boucle.

// Exemple : Filtrer pour ne garder que les lignes où la 7ème colonne
// (indice 6 car les indices commencent à 0) n'est PAS égale à 4.
// On utilise une fonction fléchée (=>) pour plus de concision (ES6+).
const filteredValues = values.filter(row => {
  // 'row' représente une ligne complète du tableau 'values'
  // row[6] accède à la 7ème valeur de cette ligne (indice 6)
  return row[6] != 4; // Retourne 'true' si la condition est remplie (la ligne est gardée)
});

Logger.log(`Nombre de lignes initiales: ${values.length}`);
Logger.log(`Nombre de lignes après filtrage: ${filteredValues.length}`);
  • Explication pour débutants : filter parcourt chaque row (chaque ligne) du tableau values. Pour chaque ligne, il exécute la petite fonction row => row[6] != 4. Si cette fonction renvoie true (la condition est vraie), la ligne est ajoutée au nouveau tableau filteredValues. Sinon, elle est ignorée.
  1. Boucler sur le tableau (filtré ou non) : Maintenant, parcourez le tableau filteredValues (ou values si vous n’avez pas filtré). C’est très rapide car tout se passe en mémoire.

Avec une boucle for…of :

// Boucle sur chaque 'rowData' (chaque ligne) dans le tableau filtré
for (const rowData of filteredValues) {
  // Accédez aux données via les indices du tableau (commencent à 0)
  // IMPORTANT : Les indices ici correspondent aux colonnes DANS LA PLAGE LUE.
  // Si vous avez lu à partir de la colonne A (indice 0):
  const agent = rowData[2];      // 3ème colonne lue (indice 2) -> Colonne C
  const secteur = rowData[4];    // 5ème colonne lue (indice 4) -> Colonne E
  const facturation = rowData[5]; // 6ème colonne lue (indice 5) -> Colonne F

  // --- Faites vos traitements ici ---
  Logger.log(`Agent: ${agent}, Secteur: ${secteur}, Facturation: ${facturation}`);
  // Exemple: Envoyer un email, créer un document, etc.
  // Utilities.sleep(1000); // Pause d'1 seconde si besoin entre les traitements longs
}

Avec forEach :

// Boucle sur chaque ligne et récupère aussi l'indice 'index'
filteredValues.forEach((rowData, index) => {
  // rowData : la ligne actuelle (un tableau)
  // index : l'indice de cette ligne dans filteredValues (0, 1, 2...)

  const agent = rowData[2];
  const secteur = rowData[4];
  const facturation = rowData[5];

  // --- Faites vos traitements ici ---
  Logger.log(`Ligne ${index}: Agent: ${agent}, Secteur: ${secteur}`);
});

Alternative : Utiliser getDataRange() pour inclure l’en-tête

Une autre approche courante est d’utiliser sheet.getDataRange().getValues(). getDataRange() sélectionne automatiquement toutes les cellules contenant des données dans la feuille, y compris la ligne d’en-tête.

Ensuite, vous pouvez facilement séparer l’en-tête des données réelles.

const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName("dataset");

if (!sheet || sheet.getLastRow() < 1) { // Vérifie juste s'il y a au moins une ligne
  Logger.log("Feuille vide ou inexistante.");
  return;
}

// 1. Récupère TOUT (en-tête + données)
const allValues = sheet.getDataRange().getValues();

// 2. Sépare l'en-tête des données
const header = allValues[0]; // La première ligne (indice 0) est l'en-tête
const dataValues = allValues.slice(1); // Crée un nouveau tableau SANS la première ligne

Logger.log("En-tête: " + header.join(", "));
Logger.log(`Nombre de lignes de données: ${dataValues.length}`);

// 3. Filtre les données (si nécessaire)
const filteredData = dataValues.filter(row => row[6] != 4); // Condition sur la 7ème colonne (indice 6)

// 4. Boucle sur les données filtrées
for (const rowData of filteredData) {
  const agent = rowData[2];      // Indice 2 -> 3ème colonne
  const secteur = rowData[4];    // Indice 4 -> 5ème colonne
  const facturation = rowData[5]; // Indice 5 -> 6ème colonne

  // --- Traitement ---
  Logger.log(`Traitement: ${agent}, ${secteur}`);
}

Cette méthode getDataRange().getValues().slice(1) est souvent considérée comme plus simple car vous n’avez pas à calculer startRow, numRows, etc.

Les gains de performance sont énormes !

L’impact de cette optimisation est spectaculaire. Un script qui prenait plus de 6 minutes et se bloquait avec l’ancienne méthode (cellule par cellule) peut s’exécuter en quelques secondes (souvent moins de 10 secondes) avec la méthode des tableaux, même avec des milliers de lignes !

Le temps exact peut varier un peu selon la charge des serveurs Google, mais le gain est immense et rend vos scripts capables de gérer de gros volumes de données sans problème.

Conclusion

Utiliser getValues() pour lire les données en bloc dans un tableau est essentiel pour écrire des scripts Google Apps Script performants, surtout si vous travaillez avec beaucoup de données.

Même si cela demande de s’habituer à penser en termes d’indices de tableau (qui commencent à 0) plutôt qu’en numéros de lignes/colonnes de la feuille (qui commencent à 1), les avantages en termes de vitesse et de fiabilité sont considérables.

Adoptez cette technique ! Vos scripts seront :

  • Beaucoup plus rapides.
  • Plus fiables (moins de risques de dépasser le temps limite).
  • Capables de traiter des jeux de données bien plus importants.

N’hésitez pas à intégrer ces méthodes (getValues(), filter(), for…of, forEach) dans vos prochains projets Apps Script.