Pewarta Proxy

📋 Server Request & Proxy Logs

Loading active proxy settings...
Developed by Khairul Adha (adhexa.id)
Mengambil log server...

⚙ Pengaturan Proxy Target

23 octobre 2022

Introduction: callbacks

Nous utilisons ici des méthodes du navigateur dans les exemples

Pour dĂ©montrer l’utilisation des callbacks, des promesses et d’autres concepts abstraits, nous utiliserons certaines mĂ©thodes du navigateur : plus prĂ©cisĂ©ment, nous chargerons des scripts et effectuerons des manipulations simples de documents.

Si vous n’ĂȘtes pas familier avec ces mĂ©thodes, et que leur utilisation dans les exemples est confuse, vous pouvez lire quelques chapitres de la partie suivante du tutoriel.

Mais nous allons quand mĂȘme essayer de rendre les choses claires. Il n’y aura rien de vraiment complexe au niveau du navigateur.

De nombreuses fonctions sont fournies par les environnements hîtes JavaScript qui vous permettent de planifier des actions asynchrones. En d’autres termes, des actions que nous lançons maintenant, mais qui se terminent plus tard.

Par exemple, une de ces fonctions est la fonction setTimeout.

Il existe d’autres exemples concrets d’actions asynchrones, par exemple le chargement de scripts et de modules (nous les aborderons dans les chapitres suivants).

Regardez la fonction loadScript(src), qui charge un script avec le src donné:

function loadScript(src) {
  // crée une balise <script> et l'ajoute à la page
  // ceci fait que le script avec la src donnée commence à se charger et s'exécute une fois terminé.
  let script = document.createElement('script');
  script.src = src;
  document.head.append(script);
}

Il insĂšre dans le document une nouvelle balise, créée dynamiquement, <script src="..."> avec le src donnĂ©. Le navigateur commence automatiquement Ă  la charger et l’exĂ©cute lorsqu’elle est terminĂ©e.

Nous pouvons utiliser cette fonction comme suit :

// charger et exécuter le script au chemin donné
loadScript('/my/script.js');

Le script est exĂ©cutĂ© de maniĂšre “asynchrone”, car il commence Ă  se charger maintenant, mais s’exĂ©cute plus tard, lorsque la fonction est dĂ©jĂ  terminĂ©e.

S’il y a du code sous loadScript(...), il n’attend pas que le chargement du script soit terminĂ©.

loadScript('/my/script.js');
// le code dessous loadScript
// n'attend pas que le chargement du script soit terminé
// ...

Disons que nous devons utiliser le nouveau script dĂšs qu’il est chargĂ©. Il dĂ©clare de nouvelles fonctions, et nous voulons les exĂ©cuter.

Mais si nous le faisons immĂ©diatement aprĂšs l’appel loadScript(...), cela ne fonctionnera pas:

loadScript('/my/script.js'); // le script a "function newFunction() {
}"

newFunction(); // aucune fonction de ce type!

Naturellement, le navigateur n’a probablement pas eu le temps de charger le script. Pour l’instant, la fonction loadScript ne permet pas de suivre l’achĂšvement du chargement. Le script se charge et finit par s’exĂ©cuter, c’est tout. Mais nous aimerions savoir quand cela se produit, pour utiliser les nouvelles fonctions et variables de ce script.

Ajoutons une fonction callback comme second argument Ă  loadScript qui doit s’exĂ©cuter lorsque le script se charge :

function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;

  script.onload = () => callback(script);

  document.head.append(script);
}

L’évĂ©nement onload est dĂ©crit dans l’article Chargement des ressources: onload et onerror, il exĂ©cute essentiellement une fonction aprĂšs le chargement et l’exĂ©cution du script.

Maintenant, si nous voulons appeler de nouvelles fonctions depuis le script, nous devons l’écrire dans le callback:

loadScript('/my/script.js', function() {
  // le callback est exécuté aprÚs le chargement du script
  newFunction(); // maintenant cela fonctionne
  ...
});

C’est l’idĂ©e: le deuxiĂšme argument est une fonction (gĂ©nĂ©ralement anonyme) qui s’exĂ©cute lorsque l’action est terminĂ©e.

Voici un exemple exécutable avec un vrai script :

function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;
  script.onload = () => callback(script);
  document.head.append(script);
}

loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => {
  alert(`Cool, the script ${script.src} is loaded`);
  alert( _ ); // _ est une fonction déclarée dans le script chargé
});

C’est ce qu’on appelle un style de programmation asynchrone basĂ© sur les “callbacks”. Une fonction qui fait quelque chose de maniĂšre asynchrone doit fournir un argument callback oĂč nous mettons la fonction Ă  exĂ©cuter aprĂšs qu’elle soit terminĂ©e.

Ici nous l’avons fait dans loadScript, mais bien sĂ»r c’est une approche gĂ©nĂ©rale.

Callback imbriqué

Comment charger deux scripts de maniÚre séquentielle: le premier, puis le second aprÚs lui ?

La solution naturelle serait de placer le second appel loadScript Ă  l’intĂ©rieur du callback, comme ceci:

loadScript('/my/script.js', function(script) {

  alert(`Cool, the ${script.src} is loaded, let's load one more`);

  loadScript('/my/script2.js', function(script) {
    alert(`Cool, the second script is loaded`);
  });

});

Une fois que le loadScript externe est terminé, le callback lance le loadScript interne.

Et si nous voulons un script de plus
 ?

loadScript('/my/script.js', function(script) {

  loadScript('/my/script2.js', function(script) {

    loadScript('/my/script3.js', function(script) {
      // ...continue aprÚs que tous les scripts soient chargés
    });

  });

});

Ainsi, chaque nouvelle action se trouve dans une callback. C’est bien pour peu d’actions, mais pas pour beaucoup, donc nous verrons bientît d’autres variantes.

Gestion des erreurs

Dans les exemples ci-dessus, nous n’avons pas tenu compte des erreurs. Que se passe-t-il si le chargement du script Ă©choue ? Notre callback doit ĂȘtre capable de rĂ©agir Ă  cette situation.

Voici une version améliorée de loadScript qui suit les erreurs de chargement :

function loadScript(src, callback) {
  let script = document.createElement('script');
  script.src = src;

  script.onload = () => callback(null, script);
  script.onerror = () => callback(new Error(`Script load error for ${src}`));

  document.head.append(script);
}

Il appelle callback(null, script) en cas de chargement réussi et callback(error) dans le cas contraire.

L’utilisation:

loadScript('/my/script.js', function(error, script) {
  if (error) {
    // erreur dans le chargement du script
  } else {
    // script chargé avec succÚs
  }
});

Une fois encore, la recette que nous avons utilisĂ©e pour loadScript est en fait assez commune. C’est le style “error-first callback”.

La convention est:

  1. Le premier argument de la callback est réservé pour une erreur si elle se produit. Ensuite, callback(err) est appelé.
  2. Le deuxiÚme argument (et les suivants si nécessaire) sont pour le résultat réussi. Ensuite, callback(null, result1, result2...) est appelé.

Ainsi, la fonction unique callback est utilisée à la fois pour signaler les erreurs et pour renvoyer les résultats.

Pyramide du malheur

À premiĂšre vue, il s’agit d’un moyen viable de codage asynchrone. Et c’est effectivement le cas. Pour un ou peut-ĂȘtre deux appels imbriquĂ©s, cela semble correct.

Mais pour de multiples actions asynchrones qui se succĂšdent, nous aurons un code comme celui-ci:

loadScript('1.js', function(error, script) {

  if (error) {
    handleError(error);
  } else {
    // ...
    loadScript('2.js', function(error, script) {
      if (error) {
        handleError(error);
      } else {
        // ...
        loadScript('3.js', function(error, script) {
          if (error) {
            handleError(error);
          } else {
            // ...continue aprÚs que tous les scripts soient chargés (*)
          }
        });

      }
    });
  }
});

Dans le code ci-dessus:

  1. Nous chargeons 1.js, puis s’il n’y a pas d’erreur 

  2. Nous chargeons 2.js, puis s’il n’y a pas d’erreur 

  3. Nous chargeons 3.js, puis s’il n’y a pas d’erreur – fait autre chose (*).

Au fur et à mesure que les appels deviennent plus imbriqués, le code devient plus profond et de plus en plus difficile à gérer, surtout si nous avons du vrai code au lieu de ... qui peut inclure plus de boucles, des déclarations conditionnelles et ainsi de suite.

C’est ce qu’on appelle parfois “l’enfer du rappel” ou “la pyramide du malheur”.

La “pyramide” d’appels imbriquĂ©s croĂźt vers la droite Ă  chaque action asynchrone. BientĂŽt, elle devient incontrĂŽlable.

Donc cette façon de coder n’est pas trùs bonne.

Nous pouvons essayer d’attĂ©nuer le problĂšme en faisant de chaque action une fonction autonome, comme ceci:

loadScript('1.js', step1);

function step1(error, script) {
  if (error) {
    handleError(error);
  } else {
    // ...
    loadScript('2.js', step2);
  }
}

function step2(error, script) {
  if (error) {
    handleError(error);
  } else {
    // ...
    loadScript('3.js', step3);
  }
}

function step3(error, script) {
  if (error) {
    handleError(error);
  } else {
    // ...continue aprÚs que tous les scripts soient chargés (*)
  }
}

Vous voyez ? Il fait la mĂȘme chose, et il n’y a pas d’imbrication profonde maintenant parce que nous avons fait de chaque action une fonction sĂ©parĂ©e de haut niveau.

Cela fonctionne, mais le code ressemble Ă  une feuille de calcul dĂ©chirĂ©e. Il est difficile Ă  lire, et vous avez probablement remarquĂ© qu’il faut passer d’un morceau Ă  l’autre en le lisant. Ce n’est pas pratique, surtout si le lecteur n’est pas familier avec le code et ne sait pas oĂč sauter du regard.

De plus, les fonctions nommĂ©es step* sont toutes Ă  usage unique, elles sont créées uniquement pour Ă©viter la “pyramide du malheur”. Personne ne va les rĂ©utiliser en dehors de la chaĂźne d’action. Il y a donc un peu d’encombrement de l’espace de noms ici.

Nous aimerions avoir quelque chose de mieux.

Heureusement, il existe d’autres moyens d’éviter de telles pyramides. L’un des meilleurs moyens est d’utiliser des “promesses”, dĂ©crites dans le chapitre suivant.

Carte du tutoriel

Commentaires

lire ceci avant de commenter

  • Si vous avez des amĂ©liorations Ă  suggĂ©rer, merci de soumettre une issue GitHub ou une pull request au lieu de commenter.
  • Si vous ne comprenez pas quelque chose dans l'article, merci de prĂ©ciser.
  • Pour insĂ©rer quelques bouts de code, utilisez la balise <code>, pour plusieurs lignes – enveloppez-les avec la balise <pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepen
)