Skip to main content

Dans le développement des applications SAPUI5 Fiori, la gestion des opérations asynchrones est essentielle pour offrir une expérience utilisateur fluide et réactive. Qu’il s’agisse de récupérer des données depuis un service OData ou d’effectuer des appels API, l’asynchrone est au cœur de la logique de ces applications. Cependant, sa gestion peut rapidement devenir complexe et difficile à maintenir si elle n’est pas correctement maîtrisée.

Dans cet article, nous passerons en revue les principaux concepts de l’asynchrone en JavaScript et explorerons les meilleures pratiques ainsi que les erreurs courantes à éviter, afin de simplifier et d’améliorer la gestion des opérations asynchrones dans vos projets SAPUI5.

Introduction à l’asynchronie et aux promesses en JavaScript

Explication simple du concept d’asynchronie en JavaScript (bloquant vs non bloquant)

Traditionnellement, le code JavaScript s’exécute de manière synchrone, ligne par ligne, chaque instruction bloquant l’exécution du reste du code jusqu’à son achèvement. Cependant, pour les opérations longues comme les requêtes réseau, cela peut poser problème. L’asynchronie permet d’éviter de bloquer le fil d’exécution principal en traitant les tâches longues en arrière-plan, tout en laissant le reste du code continuer à s’exécuter.

Définition et rôle des promesses

Les promesses permettent de gérer l’asynchronie en JavaScript ; elles représentent le résultat ou l’échec d’une opération asynchrone. Une promesse peut être dans l’un des trois états suivants :

  • Pending (En attente) : État initial, la promesse n’est ni accomplie ni rejetée.
  • Fulfilled (Accomplie) : L’opération a réussi.
  • Rejected (Rejetée) : L’opération a échoué.

Pourquoi l’asynchronie est-elle largement utilisée dans Fiori ?

La bibliothèque de composants SAPUI5 encapsule ces concepts au sein de fonctions de rappel (callbacks). En effet, les fonctions callback « success » et « error » proviennent des promesses gérées par le framework SAPUI5. Dans la plupart des cas, il suffit de mettre en œuvre ces deux méthodes. Cependant, certaines situations nécessitent une gestion manuelle des promesses.

Parallélisation des fonctions de rappel (Promise.all)

Lorsque plusieurs appels asynchrones doivent être effectués simultanément, Promise.all permet de les exécuter en parallèle. Cela réduit le temps d’attente total en lançant toutes les requêtes en même temps et en attendant leur résolution collective.

_getProducts: function () {
  return new Promise((resolve, reject) => {
    oDataModel.read("/Products", {
      success: function (oData, oResponse) {
        resolve(oData, oResponse);
      },
      error: function (oError) {
        reject(oError);
      }
    });
  });
},

_getSites: function () {
  return new Promise((resolve, reject) => {
    oDataModel.read("/Sites", {
      success: function (oData, oResponse) {
        resolve(oData, oResponse);
      },
      error: function (oError) {
        reject(oError);
      }
    });
  });
},

_getProductsBySites: function () {
  Promise.all([
    this._getProducts(),
    this._getSites()
  ])
  .then((results) => {
    // do something with products and sites
  })
  .catch((error) => {
    // error retrieving products or sites
  });
}

Utilisation d’une fonction asynchrone avec différents résultats

La fonction « success » fournie par SAPUI5 permet d’implémenter une logique unique à exécuter après une fonction asynchrone. Cependant, il arrive que l’on souhaite réutiliser la même fonction dans différents scénarios. Dans de tels cas, utiliser une promesse avec son « then » s’avère plus pratique.

_getProductsPrices: function () {
  return new Promise((resolve, reject) => {
    oDataModel.read("/ProductsPrices", {
      success: function (oData, oResponse) {
        resolve(oData, oResponse);
      },
      error: function (oError) {
        reject(oError);
      }
    });
  });
},

_computeTotalPrice: function () {
  this._getProductsPrices().then((oData, oResponse) => {
    // sum products prices for example
  });
},

_getMostExpensiveProduct: function () {
  this._getProductsPrices().then((oData, oResponse) => {
    // search for the highest price for example
  });
}

Gestion de l’état des données et de l’état de l’application

L’asynchronie aide également à mieux gérer l’état de l’application, notamment en affichant des indicateurs de chargement (comme des points de chargement) pendant la récupération des données. Cela permet d’informer l’utilisateur sur l’état de l’application et d’améliorer son expérience.

Quelles sont les bonnes pratiques et les choses à éviter ?

Bonnes pratiques :

  • Gérer correctement les erreurs pour capturer toutes les exceptions potentielles.
  • Utiliser Promise.all() pour effectuer des appels parallèles lorsque cela est nécessaire.
  • Privilégier les promesses au lieu de dupliquer du code.

Choses à éviter :

  • Utiliser des callbacks imbriqués, ce qui rend le code difficile à maintenir et à déboguer.
  • Surcharger le thread principal avec trop d’opérations lourdes en parallèle sans limiter la charge.

Comparaison entre les anciennes techniques comme les callbacks et les nouvelles approches basées sur les promesses

Avec l’avènement des promesses, puis de async/await, basé sur les promesses, le code est devenu beaucoup plus linéaire et plus facile à lire. async/await permet d’écrire du code asynchrone comme s’il était synchrone, ce qui le rend encore plus lisible et réduit le risque d’erreurs.

// Example using promises with async/await
function task1() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("Task 1 completed");
      resolve();
    }, 1000);
  });
}

function task2() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("Task 2 completed");
      resolve();
    }, 1000);
  });
}

function task3() {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log("Task 3 completed");
      resolve();
    }, 1000);
  });
}

// Using async/await to handle async tasks in a more readable way
async function executeTasks() {
  await task1();
  await task2();
  await task3();
  console.log("All tasks completed");
}

executeTasks();

Amélioration de la lisibilité et de la gestion des erreurs par rapport aux callbacks imbriqués (pyramide de la mort)

Avant l’introduction des promesses, la gestion de l’asynchronie se faisait souvent à l’aide de callbacks. Cependant, cette approche posait des problèmes de lisibilité et rendait la maintenance du code difficile. L’imbrication des fonctions les unes dans les autres créait une véritable pyramide de code difficile à suivre (aussi appelée callback hell).

// Fonction pour obtenir les données du client
_getCustomerData: function () {
  return new Promise((resolve, reject) => {
    oDataModel.read("/CustomerSet", {
      success: function (oData, oResponse) {
        console.log("Customer data retrieved successfully");
        resolve(oData);
      },
      error: function (oError) {
        reject(oError);
      }
    });
  });
},

// Fonction pour obtenir les commandes d'un client
_getOrderData: function (customerId) {
  return new Promise((resolve, reject) => {
    oDataModel.read(`/OrderSet(CustomerID='${customerId}')`, {
      success: function (oData, oResponse) {
        console.log("Order data retrieved successfully");
        resolve(oData);
      },
      error: function (oError) {
        reject(oError);
      }
    });
  });
},

// Fonction pour obtenir les produits d'une commande
_getProductData: function (orderId) {
  return new Promise((resolve, reject) => {
    oDataModel.read(`/ProductSet(OrderID='${orderId}')`, {
      success: function (oData, oResponse) {
        console.log("Product data retrieved successfully");
        resolve(oData);
      },
      error: function (oError) {
        reject(oError);
      }
    });
  });
},

// Exemple de .then() imbriqué
_getCustomerOrderProductData: function () {
  this._getCustomerData()
    .then((customerData) => {
      // Imbriquer un autre .then() pour récupérer les commandes du client
      return this._getOrderData(customerData.CustomerID)
        .then((orderData) => {
          // Imbriquer un autre .then() pour récupérer les produits de la commande
          return this._getProductData(orderData.OrderID)
            .then((productData) => {
              console.log("Final product data:", productData);
            })
            .catch((error) => {
              console.error("Error retrieving product data:", error);
            });
        })
        .catch((error) => {
          console.error("Error retrieving order data:", error);
        });
    })
    .catch((error) => {
      console.error("Error retrieving customer data:", error);
    });
}

Conclusion

Une bonne gestion des opérations asynchrones dans le développement d’applications SAPUI5 Fiori est essentielle pour garantir une expérience utilisateur optimale et réactive. En maîtrisant les concepts fondamentaux de l’asynchronie en JavaScript et en appliquant les bonnes pratiques, vous pouvez accélérer les performances, simplifier la gestion des appels réseau et améliorer la lisibilité du code. L’utilisation appropriée de mécanismes tels que les promesses, Promise.all et la nouvelle syntaxe async/await permet de rendre votre code plus robuste et plus facile à maintenir. En appliquant ces principes à vos projets SAPUI5, vous offrirez non seulement des applications performantes mais vous faciliterez également la gestion et l’évolution de vos solutions sur le long terme.

Nolan Fievet

Author Nolan Fievet

More posts by Nolan Fievet